zoukankan      html  css  js  c++  java
  • Large Class--过大的类--要重构的信号

    如果想利用单个类做太多事情,其内往往就会出现太多实例变量。一旦如此,Duplicated Code也就接踵而至。
     
     
    解决方法:
     
     
    1.将类内彼此相关的变量,将它们放在一起。使用Extract Class手法,将彼此相关的变量提炼到新的类。
     
     
    2.如果1中的新类适合作为一个子类,那么可以使用Extract Subclass手法。
     
    11.对于太多代码的处理办法,分解函数,将大函数分解成若干小函数,这样可以消除重复代码。相关的函数,可以跟着变量,一起被提炼到一个新的类中去,使用Extract Class手法或者Extract Subclass。
     
    12.实现11时,要确定客户端如何使用提炼的类,然后,使用Extract Interface手法,为每种使用方式提炼出一个接口。
     
    111.如果当前过大的类是GUI类,那么,新提炼的类和当前类,两边要各保留一些重复数据,并保持同步。这要使用Duplicate Observed Data手法。
     
    具体处理手法介绍:
     
     
    1. Extract Subclass
    类中的某些特性只被某些(而非全部)实例用到。
     
    出现这种特征,可以创建一个子类,该子类只使用这部分特性。这样,就把子类专有的特性从原来的类中
    提炼出来,从而让原来的类变得不那么大。
     
    重点是要识别出这种情况的存在,比如:Job Item有一种使用情况,只是使用getUnitPrice,getEmployee,
    不使用Job Item的其它特性。那么,就可以把使用getUnitPrice,getEmployee提炼为一个类,在这里使用
    Extract Subclass手法。
     
    再比如:
    1: public class Registration
    
       2: {
    
       3:     public NonRegistrationAction Action { get; set; }
    
       4:     public decimal RegistrationTotal { get; set; }
    
       5:     public string Notes { get; set; }
    
       6:     public string Description { get; set; }
    
       7:     public DateTime RegistrationDate { get; set; }
    
       8: }
    
     
     
    这个类,有两个使用场景,一个是注册的;一个是非注册的。作为注册实例的时候,它只使用到RegistrationDate,Description,RegistrationTotal方法,没有使用其它方法;
    而作为非注册实例来使用的时候,只是使用NonRegistrationAction,Notes方法。
     
    符合特征,类中的某些特性只被某些(而非全部)实例用到。这里如果使用Extract Subclass手法, 那么,
    处理后,结果是这样:
       1: public class Registration
    
       2: {
    
       3:     public decimal RegistrationTotal { get; set; }
    
       4:     public string Description { get; set; }
    
       5:     public DateTime RegistrationDate { get; set; }
    
       6: }
    
       7:  
    
       8: public class NonRegistration : Registration
    
       9: {
    
      10:     public NonRegistrationAction Action { get; set; }
    
      11:     public string Notes { get; set; }
    
      12: }
    
     
    2.Extract Interface
     
    “使用一个类”的含义解读,下述情况之一:
    1).使用该类的所有责任区。
    2).某一组客户只使用类责任区中的一个特定子集。
    3).该类需要与所有协助处理某些特定请求的类协作。
     
    对于2),3)的情况,将一个类中的这部分责任分离出来,这样有意义。因为,
    这样可以更容易看清楚类的责任划分。
     
    所以,Extract Interface手法做的事情,就是把原来类中的部分子集,部分责任分离出来。
     
    例子:
    Employee类,提供了很多功能,但是,有部分类,只使用它的getRate()和hasSpecialSkill()功能。
    那么,为了让Employee类变小,可以把它的这两个功能,getRate()和hasSpecialSkill()提炼出来。提炼为接口
    interface Billable{
     
      public int getRate();
      public boolean hasSpecialSkill();
    }
     
    然后Employee类实现接口Billable。
     
    这样,那些只需要使用到Employee类getRate(),hasSpecialSkill()功能的类,只需要使用接口Billable就可以,这样,只是暴露了Billable功能给使用者。也就是,只给使用者提供它们可以使用的功能,其它功能子集无需提供。
     
    多个使用者,只关注getRate(),hasSpecialSkill()功能,不同的使用者,会要求不同的实现。这时,就可以进行不同的实现来满足使用者的要求。
    3.Duplicate Observed Data(复制“被监视的数据”)
    场景:
    一个GUI类,包含了业务处理代码和用户界面代码。当业务逻辑的增加, 会让这个类越来越大,而且逻辑会复杂。这是因为,一个GUI类同时承担了两种责任,业务处理功能和界面渲染功能。这时候,就需要把业务处理代码从GUI类中分离出来。
     
    业务处理代码和界面显示代码分离开之后,方便维护和方便开发。在进行业务处理时,需要使用到界面数据,而界面进行更新的时候,也会需要使用到业务处理的结果。这时,就需要数据在业务层和界面层之间进行同步。
     
    而这个手法,可以实现上述同能,业务层(领域层)和界面层的数据同步。
     
     
    例子:
    下面的内容都是IntervalWindow类:
     
      public class IntervalWindow extends Frame...
        java.awt.TextField _startField;
        java.awt.TextField _endField;
        java.awt.TextField _lengthField;
     
        class SymFocus extends java.awt.event.FocusAdapter
        {
            public void focusLost(java.awt.event.FocusEvent event)
            {
                Object object = event.getSource();
                if (object == _startField)
                    StartField_FocusLost(event);
                else if (object == _endField)
                    EndField_FocusLost(event);
                else if (object == _lengthField)
                    LengthField_FocusLost(event);
            }
        }
    

    一个GUI界面,有三个文本框,当某个文本框输入内容,离开时,也就是失去焦点的时候,会触发一个计算。
    Start文本框失去焦点的时候,执行StartField_FocustLost方法;
    End文本框失去焦点的时候,执行EndField_FocusLost方法;
    Length文本框失去焦点的时候,执行LengthField_FocusLost方法。
     
    Start文本框和End文本框,失去焦点时,做的事情是,检查文本框的输入是否是整数,如果不是,则将文本框的内容设置为0;然后,计算Length文本框的内容。
     
    Length文本框失去焦点的时候,做的事情是,检查文本框的输入是否是整数,如果不是,则将文本框的内容设置为0;然后,计算当前文本框的值。
    这是界面操作代码。
     
        void StartField_FocusLost(java.awt.event.FocusEvent event) {
            if (isNotInteger(_startField.getText()))
                _startField.setText("0");
            calculateLength();
        }
     
        void EndField_FocusLost(java.awt.event.FocusEvent event) {
            if (isNotInteger(_endField.getText()))
                _endField.setText("0");
            calculateLength();
        }
     
        void LengthField_FocusLost(java.awt.event.FocusEvent event) {
            if (isNotInteger(_lengthField.getText()))
                _lengthField.setText("0");
            calculateEnd();
        }
    
    void calculateLength(){
          try {
            int start = Integer.parseInt(_startField.getText());
            int end = Integer.parseInt(_endField.getText());
            int length = end - start;
            _lengthField.setText(String.valueOf(length));
          } catch (NumberFormatException e) {
            throw new RuntimeException ("Unexpected Number Format Error");
          }
    }
    void calculateEnd() {
        try {
          int start = Integer.parseInt(_startField.getText());
          int length = Integer.parseInt(_lengthField.getText());
          int end = start + length;
          _endField.setText(String.valueOf(end));
        } catch (NumberFormatException e) {
          throw new RuntimeException ("Unexpected Number Format Error");
        }
    }
    
    calculateLength()方法和calculateEnd()方法,它包含了计算的逻辑,还包含了对界面组件的引用。我们现在想做的事情,就是,让这两个方法与界面组件(_startField,_endField,_lengthField)解耦。
     
    这里就可以使用到Duplicate Observed Data手法。创建一个领域类,领域类和界面类的数据同步;领域类的数据发生更新后,会将结果传递给界面类。
     
    -------------------------------------------------------------------------------------------------------
    应用Duplicate Observed Data手法之后,得到如下代码:
     
    整个流程就变成这样:
    1.GUI类,IntervalWindow初始化时,使用的是Interval这个类的值。
    2.当GUI类发生事件变化的时候,把组件的值传递给_subject实例,并进行计算。
    3._subject计算完毕,再通知GUI类界面进行更新。
     
    在这里,计算的逻辑,被分割到_subject实例(领域类)。
    数据的传递流程:
     
    领域类,界面初始值数据------>{界面生成(GUI类)------>领域类------>界面}------>{界面生成(GUI类)--->领域类--->界面}
     
     
     
     
    public class IntervalWindow extends Frame implements Observer{
     
     private Interval _subject;
     
      public IntervalWindow(){
     
        _subject = new Interval();
        _subject.addObserver(this);
        update(_subject, null);
        
         }
     
      String getEnd() {
            return _subject.getEnd();
        }
     
        void setEnd (String arg) {
            _subject.setEnd(arg);
        }
     
     
     
     
     
     
       void StartField_FocusLost(java.awt.event.FocusEvent event) {
            if (isNotInteger(_startField.getText()))
                _startField.setText("0");
              
            _subject.setStart(_startField.getText());
            _subject.calculateLength();
        }
     
        void EndField_FocusLost(java.awt.event.FocusEvent event) {
            if (isNotInteger(_endField.getText()))
                _endField.setText("0");
     
            _subject.setEnd (_endField.getText());
            _subject.calculateLength();
        }
     
        void LengthField_FocusLost(java.awt.event.FocusEvent event) {
            if (isNotInteger(_lengthField.getText()))
                _lengthField.setText("0");
     
           _subject.setLength(_lengthField.getText());
            _subject.calculateEnd();
        }
     
     
     
       @Override
       public void update(Observable observed, Object arg) {
        
           _endField.setText(_subject.getEnd());
           _startField.setText(_subject.getStart());
           _lengthField.setText(_subject.getLength());
       }
    }
     
     
    -------------------------------
     
     class Interval extends Observable {
     
        private String _end = "0";
        private String _start = "0";
        private String _length ="0";
     
     
        String getEnd() {
            return _end;
        }
        void setEnd (String arg) {
            _end = arg;
            setChanged();
            notifyObservers();
        }
     
     
        String getStart(){
       
           return _start;
        }
       void setStart(String arg){
            
             _start = arg;
             setChanged();
             notifyObservers();
        }
     
     
       String getLength(){
       
           return _length;
        }
       void setLength(String arg){
            
             _length = arg;
             setChanged();
             notifyObservers();
        }
     
          void calculateLength(){
          try {
            int start = Integer.parseInt(getStart());
            int end = Integer.parseInt(getEnd());
            int length = end - start;
            setLength(String.valueOf(length));
          } catch (NumberFormatException e) {
            throw new RuntimeException ("Unexpected Number Format Error");
          }
        }
     
        void calculateEnd() {
          try {
            int start = Integer.parseInt(getStart());
            int length = Integer.parseInt(getLength());
            int end = start + length;
            setEnd(String.valueOf(end));
          } catch (NumberFormatException e) {
            throw new RuntimeException ("Unexpected Number Format Error");
          }
      }
     
    
    
    }
     
    参考资料:
    https://sourcemaking.com/refactoring/duplicate-observed-data
    https://lostechies.com/seanchambers/2009/08/20/refactoring-day-20-extract-subclass/
    http://www.refactoring.com/catalog/duplicateObservedData.html
    http://www.refactoring.com/catalog/extractSubclass.html
  • 相关阅读:
    WebClient 非阻塞客户端 RestTemplate 阻塞式客户端
    微服务网关---调用其他微服务
    复习下comparable和comparator以及比较
    关于InitializingBean的用法、应用
    Scheduled(cron = "")
    windows查看进程方法(老是忘只能写了)
    vue 控件component
    vue 过滤器的使用实例
    vue基础
    日志脱敏工具
  • 原文地址:https://www.cnblogs.com/ttylinux/p/4567940.html
Copyright © 2011-2022 走看看