类的规范

首先在介绍类的规范的时候,必须介绍下SOLID设计原则。在我们实际开发过程中,需要把SOLID作为一个整体,而不是单独看单个原则。

  • 单一职责是所有设计原则的基础。
  • 开闭原则是设计的终极目标。
  • 里氏替换原则强调的是之类替换父类后程序运行的正确性,它用来帮助实现开闭原则。
  • 接口隔离原则用来帮助实现里氏替换原则,同时它也体现了单一职责。
  • 依赖倒置原则是过程式编码与OO编码的分水岭,同时它也被用来指导接口隔离原则。

一. SOLID设计原则。

1. 单一职责原则(Single Responsibility Principle)

类或模块应有且只有一条加以修改的理由,系统应该有许多短小的类而不是少量巨大的类组成。每个小类封装一个职责,并与少数其他类一起协同达到期望的系统行为。

// 下面实例用户的属性和行为没有分离,应将其拆分成两个接口
public interface IUserInfo {
    Long getUserId();
    void setUserId(Long userId);
    String getName();
    void setName(String name);
    String getPassword();
    void setPassword(String password);

    boolean changePassword(String oldPassword);
    boolean deleteUser();
    boolean addRoleId(Long roleId);
}

// 拆分1:负责用户的属性
public interface IUserBO {
    Long getUserId();
    void setUserId(Long userId);
    String getName();
    void setName(String name);
    String getPassword();
    void setPassword(String password);
}

// 拆分2:负责用户的行为
public interface IUserService {
    boolean changePassword(IUserBO userInfo, String oldPassword);
    boolean deleteUser(IUserBO userInfo);
    boolean addRoleId(IUserBO userInfo, Long roleId); 
}

2. 开放-闭合原则(Open Closed Principle)。

类应该对扩展开发,对修改封闭。通过增加代码来扩展功能,而不是修改已经存在的代码。怎么来实现了?就是抽象,把已有的业务功能抽象成接口,对于修改的内容通过多态来实现扩展。这个原则是写代码里面我个人认为最重要的,下面我举两个例子。

  • 例子1:
// 下面代码有两种发消息方式,如果后面又增加一种微信的方式,要修改sendMessage类增加sendByWeChat()方法,还要修改调用方逻辑。
public class SendMessage() {
    public boolean sendBySMS(String address, String content) {
        
    }
    
    public boolean sendByEmail(String address, String title, String content) {
        
    }
    
}

// 修改后:抽象一个发消息的接口,让发SMS和Email实现这个接口,增加Wechat只需新增实现就行,这样就不需要已有的接口定义和实现类了
public interface SendMessage() {
    boolean sendMessage(Message message);
}

3. 里式替换原则

里式替换原则说的是派生类(之类)对象能够替换其基类(父类)对象被使用。之类本来就可以替换父类,为什么还要里式替换原则了?这里强调的不是编译错误,而是程序运行的正确性。

public class Rectangle { 
  double width; 
  double height; 
   
  public double area() { 
    return width * height; 
  } 
} 
 
public class Square extends Rectangle { 
  public void setWidth(double width) { 
    this.width = width; 
    this.height = width; 
  } 
   
  public void setHeight(double height) {  
    this.height = width; 
    this.width = width; 
   } 
 } 
  
 public void testArea(Rectangle r) {  
   r.setWidth(5); 
   r.setHeight(4);
   // 如果r是一个正方形,则面积为16,而不是期望的20,显然结果不对。
   assert(r.area() == 20); 
} 

4. 接口隔离原则(The Interface Segregation Principle)

接口隔离原则含义是建立单一接口,不要建立庞大臃肿的接口,尽量细化接口,接口中的方法尽量少。在程序设计中,依赖几个专用的接口要比依赖一个综合的接口更灵活。接口之所以存在是为了解耦。开发者经常有个错误的认知,以为是实现类需要接口。其实是消费者需要接口,实现类只是提供服务,因此应该有消费者(客户端)来定义接口。

// 砖头可以被工人用来盖房子,也可以被用来正当防卫
public class Brick {
    private int length;
    private int width;
    private int height;
    private int weight;
    
    public void build() {
        // 工人盖房
    }
    
    public void defense() {
        // 正当防卫
    }
}

// 提取以下接口,普通大众需要的是正当防卫,并不需要盖房子。当普通大众被迫依赖了自己不需要的接口方法时,就违反接口隔离原则了。
public interface BrickInterface {
    void buildHouse();
    void defense();
}

// 站在消费这角度,抽象出接口
public interface BuildHouse {
    void build();
}

public interface StrickCompetence {
    void defense();
}

// 普通人只需正当防卫
public class Person implements BuildHouse {
    
}

// 工人建造房子
public class Worker implements StrickCompetence {
    
}

5. 依赖倒置原则(Dependence Inversion Principle)

高层模块不应该依赖低层模块,二者都应该依赖其抽象。抽象不依赖于细节,细节应该依赖于抽象。依赖倒置原则的核心思想面向接口编程。

// 妈妈拿着书给孩子讲故事
public class Book {
    public String getContent() {
        return "从前,山里有个庙,庙里有两个和尚...";
    }
}

public class Mother {
    public void narrate(Book book) {
        System.out.println("妈妈开始讲故事");
        System.out.println(book.getContent());
    }
}

// 问题:万一哪天书要换成报纸、杂志、网页了?Mother和Book强耦合在一起,这个时候还需要修改Mother。
// 修改:引入抽象的接口IReader,Mother和IReader发生依赖关系。
public interface IReader {
    String getContent();
}

public class Mother {
    public void narrate(IReader reader) {
        System.out.println("妈妈开始讲故事");
        System.out.println(reader.getContent());
    }
}

public class Book implements IReader {
    public String getContent() {
        return "从前,山里有个庙,庙里有两个和尚...";
    }
}

public class Newspaper implements IReader {
    public String getContent() {
        return "湖人总冠军...";
    }
}

实际编程注意以下三点:

  • 底层模块尽量都要有抽奖类或者接口。
  • 变量的声明类型尽量是抽象类或者接口。
  • 使用继承时遵循里式替换原则。

二. 类应该短小。

衡量类的大小,通过计算职责,类的名称应当描述其职责,如果一个类职责过多,表明这个类不够短小。要保持类“短小”应该满足单一职责原则和高内聚。

1. 参考上面单一职责原则(Single Responsibility Principle)

2. 保持高内聚

内聚性又称块内联系,指一个模块内部各个元素彼此结合的紧密程度的度量。类应该只有少量实体变量,类中的每个方法都应该操作一个或多个这种变量。方法操作的变量越多,就越黏聚到类上。保持内聚性会得到很多短小的类,

// 一个高内聚的代码示例,只有size()方法没用到两个变量,其他都用到了。
public class Stack {
    private int topOfStack = 0;
    List<Integer> elements = new LinkedList<>();
    
    public int size() {
        return topOfStack;
    }
    
    public void push(int element) {
        topOfStack++;
        elements.add(element);
    }
    
    public Integer pop() {
        if (topOfStack == 0) {
            return null;
        }
        Integer element = elements.get(--topOfStack);
        elements.remove(topOfStack);
        return element;
    }
}

这里说到高类聚,随便提下低耦合。耦合性又称块间联系,指软件系统结构中各模块间相互联系紧密程度的一种度量。举例:假设现在你程序运行的非常好,你修改其中一个类,其他20个类、接口都需要做修改,这就是高耦合的后果。

三. 为了修改而组织

对于多数系统,会一直进行修改,而每次修改都会伴随风险和产生bug,所以我们降低修改的风险就显得尤为重要。这里要提到两个准则OCP和DIP。

1. 参考上面开放-闭合原则(Open Closed Principle)。

2. 参考上面依赖倒置原则(Dependence Inversion Principle)

posted @ 2019-08-16 09:31  wudiffs  阅读(272)  评论(0编辑  收藏  举报