关于重构的一些思想

简介:

DRY原则:Don't Repeat Yourself (摘自wikipedia


OOA和OOD的作用及其区别 http://blog.sina.com.cn/s/blog_72ed42d401015y5c.html


站在为程序员提供第3方服务SDK的高度来写代码


1.抽象方法和非抽象方法

如果在类中定义了抽象方法,就是强制非抽象子类去实现。这样写的好处就是可以提醒子类需要复

写哪些方法,但是抽象方法不易过多,不需要强制实现的方法不要抽象化。


如果在类中定义了非抽象方法,可以给一个默认的实现,子类可以选择不复写,采用父类默认的实

现,也可以复写自定义实现。

应用场景:

  抽取了一个带ListView的BaseListFragment,后来又有需求,需要一个带头的ListView的

Fragment,那么,就没有必要再写一个类继承BaseListFragement了,况且继承了ListView的初始化已经

完成了,没法再加一个头。

  所以直接在BaseListFragment里定义一个方法返回头视图,默认将方法返回值至为null,在listView

初始化的时候,将方法返回值添加到listview的头部,不为空就添加到头部。


2.关于抽象的方法在哪儿被调的问题

  既然抽了方法,肯定在父类里的某个地方要让其执行。  

  如果父类有自己的生命周期方法,会自动执行一些方法,在这些方法里可以抽出方法。

  如果父类没有自己的生命周期方法,那么调用抽取的方法的父类方法要记得手动去调用。


3.关于怎么抽取的问题

  情景1:抽象适配器,适配器的的抽象方法调用适配器所在父类的方法,父类的这个方法再抽象让

      子类去实现。如我的开源中国带indicator的框架就是这么抽取的。

      这种方式就是实现了方法的抽象和转移,将内部的抽象转移成父类的抽象


4.关于电脑模型

  其实编程和现实是息息相关的,如电脑和各个零部件就是抽取的具体体现,各个零部件相互配合,但

  是又没有焊死在一起,这就是降低了耦合度。


  程序的框架包括界面框架、网络加载框架,框架在抽取的时候应该降低它们之间的依赖性。


    


如果要写出资深的重构代码,必需要“精通”以下的知识:

  1.接口

    情景1:对几个列表按时间字段进行排序,但是几个列表的实体类之间没有任何的继承关系,得到

       时间的方法也不一样,所以对于每一个列表都要定义一个比较器。

       wKiom1bBeaDw_-roAAArrWgmsy0746.png

       我在想,同样比较的是时间,为什么要定义3个比较器呢?

        于是,我定义了一个接口:

       

1
2
3
     public  interface      DateInterface {
          String getDate();
     }

        让每个列表的实体类都去实现这个接口:

      

1
2
3
4
5
6
7
public  class  BidRecordSQL  extends  Entity  implements  DateInterface{
     //...
     @Override
     public  String getDate() {
        return  investTime;
     }
}
1
2
3
4
5
6
7
public  class  BaseDepositsHistoryDomain  extends  Entity  implements  DateInterface{
     //...
     @Override
     public  String getDate() {
        return  investTime;
     }
}

       然后定义一个时间比较器:

    

1
2
3
4
5
6
7
8
9
10
/**
  * 时间比较器-降序排序
  * Created by Zhang on 2016/2/15.
  */
public  class  DescendingDateComparator  implements  Comparator<DateInterface> {
     @Override
     public  int  compare(DateInterface lhs, DateInterface rhs) {
         return  - 1  * lhs.getDate().compareTo(rhs.getDate());
     }
}

      然后,使用Collections工具类对List进行排序:

    wKioL1bBe3uAhr67AAA9eY61VYk650.png   

  使用接口,本质的作用是让原本看似不相干的实体类之间产生了关系。也就是定义相同的行为,getDate, 然后比较器针对接口编程,而不是某个具体的类。

  

  情景2:ViewPager装了多个Fragment,只想在viewpager滑动到哪一页,就更新哪一页的数据。

     一般人的做法就是在每个Fragment定义一个加载数据的方法,然后在onPageChange方法里根据

     position得到对应的Fragment,然后调用fragment的对应方法。

      如果让每一个Fragment实现一个懒加载数据的接口,那么在onPageChange就不需要根据posit       ion去if-else了,直接将fragment强转成接口,调用接口的方法即可。



  2.反射

  情景1:字段过滤器

  

1
2
3
public  interface  IPropertyFilter {
       boolean  apply(Object object, String name, Object value);
}


1
2
3
4
5
6
7
public  interface  IGetFieldMap {
     Map getFieldMap();            //所有字段名,拼出map。
     Map getFieldMap(String ...fieldNames);               //根据字段名,拼出map。
     Map getFieldMapExcept(String ...exceptFieldNames);    //除了指定的几个字段名,拼出map。
     Map getFieldMap(List<String> fieldNames);            //根据字段名,拼出map。
     Map getFieldMap(IPropertyFilter propertyFilter);     //根据过滤器,拼出map。
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
public  class  BaseRequestBean  implements  IGetFieldMap {
     @Override
     public  Map getFieldMap() {
         return  getFieldMap( new  IPropertyFilter() {
             @Override
             public  boolean  apply(Object object, String name, Object value) {
                 return  true ;
             }
         });
     }
 
     @Override
     public  Map getFieldMap(String... fieldNames) {
         return  getFieldMap(Arrays.asList(fieldNames));
     }
 
     @Override
     public  Map getFieldMapExcept( final  String... exceptFieldNames) {
         return  getFieldMap( new  IPropertyFilter() {
             @Override
             public  boolean  apply(Object object, String name, Object value) {
                 for  (String item : exceptFieldNames){
                     if (name.equals(item)){
                         return  false ;
                     }
                 }
                 return  true ;
             }
         });
     }
 
     @Override
     public  Map getFieldMap(List<String> fieldNames) {
         Map<String, Object> result =  new  HashMap();
         Class mClass = getClass();
         Field[] declaredFields = mClass.getDeclaredFields();
         for  (Field field : declaredFields) {
             String fieldName = field.getName();
             if  (!field.isAccessible()) {
                 field.setAccessible( true );
             }
             try  {
                 Object fieldValue = field.get( this );
                 if  (fieldValue ==  null continue ;
                 if  (!fieldNames.contains(fieldName))  continue ;
 
                 result.put(fieldName, fieldValue);
             catch  (IllegalAccessException e) {
                 e.printStackTrace();
             }
         }
         return  result;
     }
 
     @Override
     public  Map getFieldMap(IPropertyFilter propertyFilter) {
         Map<String, Object> result =  new  HashMap();
         Class mClass = getClass();
         Field[] declaredFields = mClass.getDeclaredFields();
         for  (Field field : declaredFields) {
             String fieldName = field.getName();
             if  (!field.isAccessible()) {
                 field.setAccessible( true );
             }
             try  {
                 Object fieldValue = field.get( this );
                 if  (!propertyFilter.apply( this , fieldName, fieldValue))   continue ;
 
                 result.put(fieldName, fieldValue);
             catch  (IllegalAccessException e) {
                 e.printStackTrace();
             }
         }
         return  result;
     }
 
}

 

  3.注解

  4.泛型

   Java泛型详解

   http://blog.csdn.net/jinuxwu/article/details/6771121

   

   获取泛型的Class  

   http://www.cnblogs.com/onlysun/p/4539472.html

 

 4-5.枚举(优化代码可读性)

   http://blog.csdn.net/lmj623565791/article/details/79278864

 


  5.自定义

   自定义View或者类有多种形式:

   1)完全自定义(复写onDraw 、onLayout)

   2)组合自定义

   3)包裹自定义(在自定义属性绑定需要操作的子View的id,在onFinishInflate方法里find处理相关的逻辑,Google的很多框架级的原生组件用的就是这个)

    如DrawerLayout,抽屉控件等。

   4)工具类中封装view,利用已有的view实现统一的业务接口。

   5)复写Android自定义的类(要求研究源码,然后才能随心所欲的复写哈。)

    实际场景1:使用ArrayAdapter这个类填充Spinner,如果ArrayAdapter的泛型是一个对象的话,最终Spinner显示的是对象的哈希值。而我真正想展示在

          Spinner上的只是ArrayAdapter泛型的某个字段而已。

    最笨的解决方法就是遍历List<T>,得到想要展示的字符串集合List<String>,再将List<String>设置给适配器。但是这样一来的话,在spinner里点击事件

    里往往又会用到List<T>,这样就容易造成混乱。

    


    于是研究了一下ArrayAdapter这个类的源码,看看它的view是如何生成的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
       public  View getView( int  position, View convertView, ViewGroup parent) {
     return  createViewFromResource(mInflater, position, convertView, parent, mResource);
}
 
private  View createViewFromResource(LayoutInflater inflater,  int  position, View convertView,
         ViewGroup parent,  int  resource) {
     View view;
     TextView text;
 
     if  (convertView ==  null ) {
         view = inflater.inflate(resource, parent,  false );
     else  {
         view = convertView;
     }
 
     try  {
         if  (mFieldId ==  0 ) {
             //  If no custom field is assigned, assume the whole resource is a TextView
             text = (TextView) view;
         else  {
             //  Otherwise, find the TextView field within the layout
             text = (TextView) view.findViewById(mFieldId);
         }
     catch  (ClassCastException e) {
         Log.e( "ArrayAdapter" "You must supply a resource ID for a TextView" );
         throw  new  IllegalStateException(
                 "ArrayAdapter requires the resource ID to be a TextView" , e);
     }
 
     T item = getItem(position);
     if  (item  instanceof  CharSequence) {
         text.setText((CharSequence)item);
     else  {
         text.setText(item.toString());
     }
 
     return  view;
}

   通过源码发现,如果ArrayAdapter的泛型是字符串,那么spinner展示的是字符串;如果ArrayAdapter的泛型是一个对象的话,返回的是这个对象的toString方法的返回值。

   解决方案1:复写ArrayAdapter的getView的相关方法。

      此解决方案的核心是将ArrayAdapter展示Spinner内容部分的具体代码抽象化成方法,从而使ArrayAdapter亦抽象化。

     但是此方式有一个弊端:每有一个泛型类,就得新建一个对应的Adapter类,太浪费资源。 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
  * Created by 陈章 on 2017/12/19.
  * 适配器
  */
public  abstract   class  CZArrayAdapter<T>  extends  ArrayAdapter{
     public  CZArrayAdapter(Context context,  List<T> objects) {
         super (context, android.R.layout.simple_spinner_item, objects);
     }
     
     @NonNull
     @Override
     public  View getView( int  position, View convertView, ViewGroup parent) {
         return  createViewFromResource(LayoutInflater.from(getContext()), position, convertView, parent);
     }
 
     @Override
     public  View getDropDownView( int  position, View convertView, ViewGroup parent) {
         return  createViewFromResource(LayoutInflater.from(getContext()), position, convertView, parent);
     }
 
     protected  abstract  String getText(T t);
 
     private  View createViewFromResource(LayoutInflater inflater,  int  position, View convertView,
                                         ViewGroup parent) {
         T item = (T) getItem(position);
         View view;
         TextView text;
 
         if  (convertView ==  null ) {
             view = inflater.inflate(android.R.layout.simple_spinner_item, parent,  false );
         else  {
             view = convertView;
         }
         text = (TextView) view;
         text.setText(getText(item));
         return  view;
     }
 
}

  

 解决方案2:复写ArrayAdapter的getView的泛型类的toString方法

      复写泛型类的toString方法,返回想在spinner上展示的字段。如果泛型类的toString方法没有在其它地方有特殊的引用,这种解决方法是最快最简单的。 


  

  6.其它,肯定有,尚未总结。  

     

怎么解耦的问题:

  1.解耦

    1)View的解耦:一般如详情页面、带有Banner图的页面,里面的View可能会有很多。可以将

     大的View细分为小的View。

     

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public  abstract  class  BasePart<T> {
    /**
     * 获取当前模块的View对象
     * @return
     */
    public  abstract  View getView();
    
    /**
     * 处理逻辑和数据
     * @param t
     */
    public  abstract  void  setData(T t);
 
    /**
     * startActivity with bundle
     *
     * @param clazz
     * @param bundle
     */
    protected  void  readyGo(Class<?> clazz, Bundle bundle) {
       Intent intent =  new  Intent(CommonHelper.context(), clazz);
       intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);     //Calling startActivity() from outside of an Activity  context requires the FLAG_ACTIVITY_NEW_TASK flag.
       if  ( null  != bundle) {
          intent.putExtras(bundle);
       }
       CommonHelper.context().startActivity(intent);
    }
}

  只要在activity里initview()的时候,new PartView(),将子View添加到Activity的ContainerView中

 ,请求到数据之后再partView.setData();


  但是PartView是一个很简单的类,如果需要Activity的参数(如调用Activity的方法等),可以在构造函数里传入Activity.



对于别人重构的框架,应该如何去用的问题:

  1.抽象类,肯定是要实现它示实现的方法。写逻辑就是对抽象方法的具体实现。


将一些公共的libs和基类放在依赖库中

  1)出现的问题:将工程的layout下的布局文件写到依赖库里,那么相关的资源color、style看着是

    明明定义了,但就是can't resolve.

  



重构的实例场景

  1)一个网络请求接口(传一个paperid得到试卷的题目)要在程序的多处调用,之前离职的程序员写

   的代码也没有封装:

   

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
/**
  * 从网络获取ListView的数据
  */
private  void  getListData(String p_id) {
     if  (!NetWorkStatusUtil.isNetworkAvailable(context)) {
         ToastUtil.showShort(context,  "请先链接网络" );
         return ;
     }
     LoadingDialog.showProgress( this "题目正在加载中,请稍等..." true );
     String url = HttpConfig.QUESTION;
     RequestParams params =  new  RequestParams();
     // params.addBodyParameter("token", MD5Encoder.getMD5());
     KLog.e( "试卷页面p_id:"  + p_id);
     params.addQueryStringParameter( "token" , MD5Encoder.getMD5());
     params.addQueryStringParameter( "p_id" , p_id);
     //p_id=2是单选;
     //http://app.haopeixun.org/index.php?g=apps&m=paper&a=getExams&token=75c824f486b5fa5b60330697bdb03842&p_id=8
     //
     HttpUtils httpUtils =  new  HttpUtils();
     httpUtils.send(HttpMethod.GET, url, params,  new  RequestCallBack<String>() {
 
         @Override
         public  void  onSuccess(ResponseInfo<String> responseInfo) {
             jsonString = responseInfo.result;
             Log.i(TAG,  "考题答案: " );
             KLog.json(jsonString);
             if  (!TextUtils.isEmpty(jsonString)) {
                 JSONObject jsonObject;
                 try  {
                     jsonObject =  new  JSONObject(jsonString);
                     int  resultCode = Integer.parseInt(jsonObject.getString( "result" ));
                     KLog.e( "结果码:"  + resultCode);
                     switch  (resultCode) {
                         case  0 :
                             if  (jsonString.isEmpty()) {
                                 showEmptyView();
                                 return ;
                             }
                             parserData(jsonString);
                             break ;
                         default :
                             showEmptyView();
                     }
                 catch  (JSONException e) {
                     showEmptyView();
                     // TODO Auto-generated catch block
                     e.printStackTrace();
                 finally  {
                     LoadingDialog.dismissProgressDialog();
                 }
             }
         }
 
         @Override
         public  void  onFailure(HttpException arg0, String arg1) {
             KLog.e(arg1);
             LoadingDialog.dismissProgressDialog();
             ToastUtil.showLong(context,  "请求考题失败" );
         }
     });
}
 
 
private  void  parserData(String jsonString) {
     // ***********给user赋值******************
     qustionItems.clear();
 
     try  {
         JSONObject jsonObject =  new  JSONObject(jsonString);
         JSONArray dataArray = jsonObject.getJSONArray( "data" );
         totalScore = dataArray.length();
         Log.i(TAG,  "总共题目数量:"  + totalScore);
         for  ( int  i =  0 ; i < dataArray.length(); i++) {
             QuestionBean questionBean =  new  QuestionBean();
             JSONObject jsonObject2 = dataArray.getJSONObject(i);
             questionBean.addtime = jsonObject2.getString( "addtime" );
             questionBean.e_analyze = jsonObject2.getString( "e_analyze" );
             questionBean.e_answer_count = jsonObject2.getString( "e_answer_count" );
             // *********
             JSONObject jsonObject3 = jsonObject2.getJSONObject( "e_answer" ); // 题选项对象
             questionBean.eanswer = questionBean. new  Eanswer();
             questionBean.eanswer.setA(jsonObject3.getString( "A" ));
             questionBean.eanswer.setB(jsonObject3.getString( "B" ));
             String answerC = jsonObject3.optString( "C" );
             if  (answerC !=  null  && !TextUtils.isEmpty(answerC)) {
                 questionBean.eanswer.setC(answerC);
             }
 
             String answerD = jsonObject3.optString( "D" );
             if  (answerD !=  null  && !TextUtils.isEmpty(answerD)) {
                 questionBean.eanswer.setD(answerD);
             }
 
             switch  (Integer.parseInt(questionBean.e_answer_count)) {
                 case  5 :
                     questionBean.eanswer.setE(jsonObject3.getString( "E" ));
                     break ;
                 case  6 :
                     questionBean.eanswer.setE(jsonObject3.getString( "E" ));
                     questionBean.eanswer.setF(jsonObject3.getString( "F" ));
                     break ;
                 case  7 :
                     questionBean.eanswer.setE(jsonObject3.getString( "E" ));
                     questionBean.eanswer.setF(jsonObject3.getString( "F" ));
                     questionBean.eanswer.setG(jsonObject3.getString( "G" ));
                     break ;
             }
             // **********
 
             questionBean.e_classid = jsonObject2.getString( "e_classid" );
             questionBean.e_exam_type = jsonObject2.getString( "e_exam_type" );
             questionBean.e_fenzhi = jsonObject2.getString( "e_fenzhi" );
             questionBean.e_good = jsonObject2.getString( "e_good" );
             questionBean.e_id = jsonObject2.getString( "e_id" );
             questionBean.e_probability = jsonObject2.getString( "e_probability" );
             questionBean.e_result = jsonObject2.getString( "e_result" );
             questionBean.e_status = jsonObject2.getString( "e_status" );
             questionBean.e_title = jsonObject2.getString( "e_title" );
             questionBean.e_typeid = jsonObject2.getString( "e_typeid" );
             questionBean.examid = jsonObject2.getString( "examid" );
             questionBean.id = jsonObject2.getString( "id" );
             questionBean.paperid = jsonObject2.getString( "paperid" );
             qustionItems.add(questionBean);
             StringBuilder sb =  new  StringBuilder();
             //sb.append("");
             uAnswerList.add(sb);
         }
     catch  (JSONException e) {
         e.printStackTrace();
     finally  {
         LoadingDialog.dismissProgressDialog();
     }
     showDataView();
     KLog.e( "集合大小"  + qustionItems.size());
     if  (qustionItems.size() >  0 ) {
         tv_total_questions.setText( "1 / 共"  + qustionItems.size() +  "道题" );
     }
     questionPagerAdapter.set(qustionItems, uAnswerList);
     uAnswerStateAdapter.set(getAnswerCard());
}

   同样的接口,我觉得没有必要再往新的activity复制一遍,于是我将这个接口单独封装成一个类

  PaperListRequester中

    wKiom1fY3ayzeppZAATxixA3XCk492.png    在构造PaperListRequester这个类的时候,我将Context和接口需要的参数pid传递进去。定义了

一个监听器ParseListener,监听内部数据解析的结果。通过一个方法getData,并传递一个监听器返回结果。

   这是调用PaperListRequester这个类请求接口的代码:

 wKiom1fY3rugzIqDAAJwJDZPips216.png   将请求、解析接口的逻辑封装在PaperListRequester这个类中,一下代码就看得顺眼了。这也是Google及一些开源库常用的工具类封装手法。

   2)工程中有个ActivityA,作用是取一个数据,但是这个取数据的过程,与activity里的handler相关联。代码量也比较多。这时候有个ActivityB,需要和

     ActivityA重复一样的工作,但是ActivityB本身类里逻辑比较复杂,如果直接把ActivityA里的代码粘贴过来,会严重影响这个类的结构。所以这里    

    就需要新建一个类C.java,用来封装ActivityA的逻辑,通过接口返回数据。在ActivityB里调用C,只管请求和取数据,中间的过程交由C.java去实现。

    实现解耦。

   





      本文转自屠夫章哥  51CTO博客,原文链接:http://blog.51cto.com/4259297/1699714,如需转载请自行联系原作者



相关文章
|
存储 缓存 算法
【ROS】如何让ROS中节点获取数据 III --参数服务器通信及ros常用工具指令介绍
相较于之前的通信模型,参数服务器是最为简单的。在之前的模型中,ROSMASTER都是扮演一个帮二者连接在一起的桥梁。
701 0
|
6月前
|
存储 前端开发 UED
08.HarmonyOS Next响应式布局秘籍:掌握Flex换行与对齐技术
在当今多设备、多屏幕尺寸的应用环境中,响应式布局已成为前端开发的核心技能。HarmonyOS Next作为面向全场景的操作系统,其UI框架提供了强大的响应式布局能力,使应用能够在手机、平板、智能手表等不同设备上呈现最佳效果。
187 2
|
计算机视觉 Python
OpenCV中拆分通道、合并通道、alpha通道的讲解及实战演示(附python源码 超详细)
OpenCV中拆分通道、合并通道、alpha通道的讲解及实战演示(附python源码 超详细)
1180 0
|
12月前
|
JSON 前端开发 网络架构
鸿蒙开发:一文探究Navigation路由组件
如果你还在使用router做为页面跳转,建议切换Navigation组件作为应用路由框架,不为别的,因为官方目前针对router已不在推荐。
640 101
鸿蒙开发:一文探究Navigation路由组件
|
存储 前端开发 安全
Tauri 开发实践 — Tauri 原生能力
本文介绍了如何使用 Tauri 框架构建桌面应用,并详细解释了 Tauri 提供的原生能力,包括文件系统访问、系统托盘、本地消息通知等。文章通过一个具体的文件下载示例展示了如何配置 Tauri 来使用文件系统相关的原生能力,并提供了完整的代码实现。最后,文章还提供了 Github 源码链接,方便读者进一步学习和参考。
601 1
Tauri 开发实践 — Tauri 原生能力
|
数据采集 搜索推荐 UED
异步渲染对 SEO 的影响及应对策略
【10月更文挑战第23天】异步渲染在提升用户体验和性能的同时,确实会给 SEO 带来一定的挑战。但通过合理运用预渲染、提供静态版本、设置爬虫抓取时间、优化页面结构和内容以及使用服务端渲染等策略,可以有效地解决这些问题,实现 SEO 和用户体验的双赢。在未来的网页开发中,我们需要更加注重异步渲染技术与 SEO 的协调发展,以适应不断变化的网络环境和用户需求。
|
数据采集 JSON JavaScript
如何通过PHP爬虫模拟表单提交,抓取隐藏数据
本文介绍了如何使用PHP模拟表单提交并结合代理IP技术抓取京东商品的实时名称和价格,特别是在电商大促期间的数据采集需求。通过cURL发送POST请求,设置User-Agent和Cookie,使用代理IP绕过限制,解析返回数据,展示了完整代码示例。
274 3
如何通过PHP爬虫模拟表单提交,抓取隐藏数据
|
7月前
|
存储 弹性计算 人工智能
阿里云服务器ECS实例规格选型指南:根据使用场景选择合适的配置
随着云计算的快速发展,阿里云提供了丰富多样的云服务器ECS实例规格,满足不同用户需求。然而,面对众多选项,新手往往难以抉择。本文详细解析八大业务场景,包括新手入门、网站业务、数据库、大数据、游戏、视频、AI机器学习及高性能计算等,帮助用户精准选择合适的实例规格。通过了解各实例的硬件配置与软件优化特点,用户可实现资源高效利用与成本合理控制,推动业务发展。更多详情与性能参考可访问阿里云官方文档。
399 17
Saga模式在分布式系统中如何保证事务的隔离性
Saga模式在分布式系统中如何保证事务的隔离性
293 7

热门文章

最新文章