设计原则之旅(一):单一职责

简称:

单一职责原则的英文名称是Single Responsibility Principle, 简称RSP。

定义:

RSP 的原话解释是:There should never be more than one reason for a class to change. ( 不要存在多于一个导致类变更的原因 ) 。通俗的说,即一个类只负责一项职责,而不应该同时负责多个职责。

问题由来:

类 T 负责两个不同的职责:职责 P1,职责 P2。当由于职责 P1 需求发生改变而需要修改类 T 时,有可能会导致原本运行正常的职责 P2 功能发生故障。

解决方案:

遵循单一职责原则。分别建立两个类 T1、T2,使 T1 完成职责 P1 功能,T2 完成职责 P2 功能。这样,当修改类 T1 时,不会使职责 P2 发生故障风险;同理,当修改 T2 时,也不会使职责 P1 发生故障风险。

举例说明

**需求:**实现一个购物车功能,购物车主要用于存放图书,根据书的价格,计算所有图书的总价格。

示例代码:

图书类:

//商品类
public class Book {    
    private String name;    //商品名称
    private double price;   //商品价格
    public Book(String name, double price) {
        super();       
         this.name = name;       
          this.price = price;
    }    
    public String getName() {       
         return name;
    }    
    public void setName(String name) {       
         this.name = name;
    }   
    public double getPrice() {        
            return price;
    }    
    public void setPrice(double price) {      
          this.price = price;
    }
}

购物车类:

//购物车
public class ShoppingCart {    //存放添加的图书
    
    private List<Book> list = new ArrayList<Book>();    //添加图书
   
    public void addBook(Book book){
        list.add(book);
    };    
    //删除图书
    public void removeBook(Book book){
        list.remove(book);
    };    
    //结账
    public double checkOut(){       
             double total = 0;       
              for(Book book : list){
                total = total+book.getPrice();
            }       
         return total;
    }
}

客户端代码:

//客户端代码
public class Client {    
  public static void main(String\[\] args) {
        Book book1 = new Book("红楼梦",50);
        Book book2 = new Book("三国演义",40);
        Book book3 = new Book("西游记",30);
        Book book4 = new Book("水浒传",20);
        ShoppingCart shoppingCart = new ShoppingCart();
        
        shoppingCart.addBook(book1);
        shoppingCart.addBook(book2);
        shoppingCart.addBook(book3);
        shoppingCart.addBook(book4);        
        
        double total = shoppingCart.checkOut();
        
        System.out.println("所有图书价格为:"+total);
    }
}

输出结果:

d1.png

OK,以上代码“ 完美 ”解决客户需求。

当然,所谓的 完美 ”是建立在需求不变的情况下。很遗憾,客户的需求总是在不断变化的,程序上线以后,客户发现没有打折功能,现在,客户要求对所有的“ 红楼梦 ”图书打 8 折促销,那我们的程序改如何修改呢?

有些小伙伴应该可能会这么修改:修改 ShoppingCart 类中的 checkOut 方法,快速实现客户需求.

//购物车
public class ShoppingCart {    //存放添加的图书
    private List<Book> list = new ArrayList<Book>();    //添加图书
   
    public void addBook(Book book){
        list.add(book);
    };   
    
     //删除图书
    public void removeBook(Book book){
        list.remove(book);
    };   
    
     //结账
    public double checkOut(){       
      double total = 0;      
        for(Book book : list){  
            if("红楼梦".equals(book.getName())){
                total = total+book.getPrice()*0.8;
            }else{
                total = total+book.getPrice();
            }
        }       
         return total;
    }
}

输出结果满足客户需求

d2.png

过来几天,客户需求又有了新的变化,对所有的“ 红楼梦 ”图书打 8 折促销,所有的“ 西游记 ”打 6 折。按照之前的修改逻辑,如下:

//购物车
public class ShoppingCart {    //存放添加的图书
    
    private List<Book> list = new ArrayList<Book>();    //添加图书
    
    public void addBook(Book book){
        list.add(book);
    };   
    
     //删除图书
    public void removeBook(Book book){
        list.remove(book);
    };    
    
    //结账
    public double checkOut(){        
        double total = 0;      
          for(Book book : list){          
            if("红楼梦".equals(book.getName())){
                total = total+book.getPrice()*0.8;
            }if("西游记".equals(book.getName())){
                total = total+book.getPrice()*0.6;
            }else{
                total = total+book.getPrice();
            }
        }        
        return total;
    }
}

同样快速满足客户需求

d3.png

示例代码分析

上边的代码,初看起来很像也没太多问题,而且还能够快速满足客户端需求,对某些项目来说,快速满足客户需求是第一要素,但是,这样写存在什么隐患呢?

客户的需求总是的在不停的变化的,按照上边的设计,客户的每一次需求变动,我们多需要修改我们的 ShoppingCart 类,客户的需求变化频率越高,需求越复杂,ShoppingCart 类也改动也频繁, 复杂度也是成倍增加,而且代码的复用性很差,如果客户需要改回原来的选取,我们同样还是需要修改我们的ShoppingCart 类, 改来改去之后,我们会发现我们之前设计的 ShoppingCart 类的 checkOut 方法已经变的非常臃肿了,以至于没法维护了。

隐患分析:

上述代码之所以会存在那么多的隐患,根本的原因在于 ShoppingCart 的设计违背的“单一职责原则”与“ 开闭原则 ”(后期会讲解),checkOut 方法即承担了统计总价职责,又承担了打折优惠职责,以至于优惠职责需求变动的时候,代码统计总价的逻辑也受到相应的影响。

解决方案:

遵循单一职责原则,拆分 ShoppingCart 职责,ShoppingCart 的职责是收集图书,输出所有图书的结账时的总价格,至于打折优惠职责交给专门的类去处理。考虑的到打折优惠是一个变化的需求,所有,我们应该将这种行为提取出来,做为一个抽象接口,具体实现交给实现类去处理。

示例代码:

提取打折策略抽象接口:

//打折策略
public interface DiscountStrategy {    //打折抽象方法
    public double discount(List<Book> list);
}

实现默认打折策略:

//默认打折策略:不打折
public class DefalutDiscountStrategy implements DiscountStrategy{
    @Override
    public double discount(List<Book> list) {       
         double total = 0;        
         for(Book book : list){
                total = total+book.getPrice();
            }        
        return total;
    }
}

购物车与打折实体通过打折策略抽象接口建立关系:

//购物车
public class ShoppingCart {   
    
     //存放添加的图书
    private List<Book> list = new ArrayList<Book>();   
    
    private DiscountStrategy discountStrategy;    //添加图书
    
    public void addBook(Book book){
        list.add(book);
    };    
    
    //删除图书
    public void removeBook(Book book){
        list.remove(book);
    };   
    
     //设置打折策略
    public void setDiscountStrategy(DiscountStrategy discountStrategy) {      
          this.discountStrategy = discountStrategy;
    }    
    //结账
    public double checkOut(){       
         if(discountStrategy== null){            
             //客户没有设置打折策略,使用默认打折策略
                discountStrategy = new DefalutDiscountStrategy();
           }        
        double total = discountStrategy.discount(list);        return total;
    }
}

客户端,根据业务需求,自定义打折优惠策略

//红楼梦打 8 折策略
public class FirstDiscountStrategy implements DiscountStrategy{
    @Override
    public double discount(List<Book> list) {       
         double total = 0;       
          for(Book book : list){            
              if("红楼梦".equals(book.getName())){
                    total = total+book.getPrice()*0.8;
                }else{
                    total = total+book.getPrice();
                }
          }        
        return total;
    }
}

客户端代码:

//客户端代码
public class Client {    
    public static void main(String\[\] args) {
        Book book1 = new Book("红楼梦",50);
        Book book2 = new Book("三国演义",40);
        Book book3 = new Book("西游记",30);
        Book book4 = new Book("水浒传",20);
        ShoppingCart shoppingCart = new ShoppingCart();
        shoppingCart.addBook(book1);
        shoppingCart.addBook(book2);
        shoppingCart.addBook(book3);
        shoppingCart.addBook(book4);        
        
        //客户设置打折策略
        DiscountStrategy discountStrategy = new FirstDiscountStrategy();
        shoppingCart.setDiscountStrategy(discountStrategy);      
          
        double total = shoppingCart.checkOut();
        
        System.out.println("所有图书价格为:"+total);
    }
}

后期,客户业务需求如果变化,只需要实现 DiscountStrategy 接口,自己定义属于自己的策略,而我们的 ShoppingCart 类则无需做任何修改,ShoppingCart 的职责也非常清晰。

总结分析:

遵循单一职责原则优点:

  • 减低类的复杂度,一个类只负责一项职责了,其业务逻辑自然就变简单了。

  • 提示类的可读性,提升系统的维护性。

  • 减低需求变化代理的风险,如果遵循了单一职责,类之间的耦合性也就减低了,内聚性增强了,需求变换带来的风险自然就降低了。

欢迎关注公众号:“Android 之旅”,查看更多相关博客

邀请二维码.png