【设计模式Android】设计模式六大原则

有几天没发文章了,一直有人在公众号问我关于观察者模式的问题,所以我决定抽时间写一写关于设计模式的内容。今天先介绍一些基础的东西。


六大原则

我以前在面试别的人的时候,总是喜欢聊聊设计模式,因为总感觉功能部分都能写出来,但是代码质量和代码设计的东西熟练,才能更好地跟团队配合,方便产品的迭代。
六大原则是:

  • 单一职责原则
  • 里氏替换原则
  • 依赖倒置原则
  • 接口隔离原则
  • 迪米特原则
  • 开闭原则

也有人说是五大原则的,少了迪米特原则。乍一看,其中开发者们最熟悉的或者说听得最多的也就是开闭原则了,其它听起来都会有一些陌生。下面就一个个介绍一下。

单一职责原则

这是一个最简单,却最难做到的原则。为什么这么说呢?
它的定义只有一句话:不要存在多于一个导致类变更的原因。通俗的说,即一个类只负责一项职责。但为什么说很难做到呢,我刚才想去我的代码中找到一个比较合适的例子来说明这个问题,却没有一个具有代表性的,因为职责这个概念有些主观,接口根据职责划分。下面想个简单的例子吧。
例如一个用户系统,有姓名和年龄两个属性:

public class Person {
    private String name;
    private String age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }
    public void changeAge(String age) {

    }
    public void changeName(String age) {

    }
}

这个类建立有什么问题呢?name和age的set,get方法是数据类型,也就是业务对象,但是changeAge和changeName需要跟服务器进行交互,属于业务逻辑,甚至于在这两个方法中还需要调用set,get方法。
根据单一职责原则,我们需要将业务和数据分开:

public class PersonObj {
    private String name;
    private String age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }
}
public class PersonLogic {
    public void changeAge(String age) {

    }
    public void changeName(String age) {

    }
}

单一原则的好处:

  • 可以降低类的复杂度,一个类只负责一项职责,其逻辑肯定要比负责多项职责简单的多;
  • 提高类的可读性,提高系统的可维护性;
  • 变更引起的风险降低,变更是必然的,如果单一职责原则遵守的好,当修改一个功能时,可以显著降低对其他功能的影响。

里氏替换原则

定义1:如果对每一个类型为 T1的对象 o1,都有类型为 T2 的对象o2,使得以 T1定义的所有程序 P 在所有的对象 o1 都代换成 o2 时,程序 P 的行为没有发生变化,那么类型 T2 是类型 T1 的子类型。
定义2:所有引用基类的地方必须能透明地使用其子类的对象。

根据定义我们可以这样理解:

  • 子类必须完全实现父类的方法
  • 子类可以有自己的个性
  • 覆写或实现父类的方法时输入参数可以宽于或等于父类参数
  • 覆写或实现父类的方法时输出结果可以窄于或等于父类参数

第一点好理解,后面是什么意思呢?看个例子:

public class Father {
    public void printf(HashMap map){
        System.out.printf("父类方法");
    }
  
}
public class Son {
    public void printf(Map map){
        System.out.printf("父类方法");
    }
}

这样只要传入的参数是HashMap都是执行父类的方法,子类由于比父类参数要宽,相当于重载了printf方法。这样做的目的不容易引起逻辑的混乱

依赖导致原则

定义:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。

依赖倒置原则的核心思想是面向接口编程,我们依旧用一个例子来说明面向接口编程比相对于面向实现编程好在什么地方。场景是这样的,父亲给孩子讲故事,需要给他一本书,或一份报纸:

class Book{  
    public String getContent(){  
        return "读书";  
    }  
}  
 class NewsPaper{  
    public String getContent(){  
        return "报纸";  
    }  
}  
class Father{  
    public void read(Book book){  
        System.out.println("爸爸"+book.getContent());  
    }  
public void read(NewsPaper news){  
        System.out.println("爸爸"+news.getContent());  
    }  
}  
  
public class Client{  
    public static void main(String[] args){  
        Father f = new Father();  
        f.read(new Book()); 
         f.read(new NewsPaper());   
    }  
}  

上述代码没有什么问题,但是有一天我们再新增一个可读的东西,如pad,那我们要重新写一个pad类,同时Father类还要新增一个read方法。如果哪天再增加一个类,还有做如上处理,太麻烦了。
我们可以这样优化一下:

interface IReader{  
    public String getContent();  
}  

然后让Book Newspaper Pad类都实现这个接口,这样父类只需要写一个方法即可:

class Father{  
    public void read(IReader reader){  
        System.out.println("爸爸"+reader.getContent());  
    }  

}  

接口隔离原则

定义:客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。
简单翻译一下这句话,就是一个类去实现接口的时候,不应该去实现他不需要的方法。

interface I {  
    public void method1();  
    public void method2();  
    public void method3();  
    public void method4();  
    public void method5();  
}  
  
class A{  
    public void depend1(I i){  
        i.method1();  
    }  
    public void depend2(I i){  
        i.method2();  
    }  
    public void depend3(I i){  
        i.method3();  
    }  
}  
  
class B implements I{  
    public void method1() {  
        System.out.println("类B实现接口I的方法1");  
    }  
    public void method2() {  
        System.out.println("类B实现接口I的方法2");  
    }  
    public void method3() {  
        System.out.println("类B实现接口I的方法3");  
    }  
    public void method4() {}  
    public void method5() {}  
}  
  
class C{  
    public void depend1(I i){  
        i.method1();  
    }  
    public void depend2(I i){  
        i.method4();  
    }  
    public void depend3(I i){  
        i.method5();  
    }  
}  
  
class D implements I{  
    public void method1() {  
        System.out.println("类D实现接口I的方法1");  
    }  
    //对于类D来说,method2和method3不是必需的,但是由于接口A中有这两个方法,  
    //所以在实现过程中即使这两个方法的方法体为空,也要将这两个没有作用的方法进行实现。  
    public void method2() {}  
    public void method3() {}  
  
    public void method4() {  
        System.out.println("类D实现接口I的方法4");  
    }  
    public void method5() {  
        System.out.println("类D实现接口I的方法5");  
    }  
}  
  
public class Client{  
    public static void main(String[] args){  
        A a = new A();  
        a.depend1(new B());  
        a.depend2(new B());  
        a.depend3(new B());  
          
        C c = new C();  
        c.depend1(new D());  
        c.depend2(new D());  
        c.depend3(new D());  
    }  
}  

可以看到,如果接口过于臃肿,只要接口中出现的方法,不管对依赖于它的类有没有用处,实现类中都必须去实现这些方法,这显然不是好的设计。如果将这个设计修改为符合接口隔离原则,就必须对接口I进行拆分。在这里我们将原有的接口I拆分为三个接口。

nterface I1 {  
    public void method1();  
}  
  
interface I2 {  
    public void method2();  
    public void method3();  
}  
  
interface I3 {  
    public void method4();  
    public void method5();  
}  
  
class A{  
    public void depend1(I1 i){  
        i.method1();  
    }  
    public void depend2(I2 i){  
        i.method2();  
    }  
    public void depend3(I2 i){  
        i.method3();  
    }  
}  
  
class B implements I1, I2{  
    public void method1() {  
        System.out.println("类B实现接口I1的方法1");  
    }  
    public void method2() {  
        System.out.println("类B实现接口I2的方法2");  
    }  
    public void method3() {  
        System.out.println("类B实现接口I2的方法3");  
    }  
}  
  
class C{  
    public void depend1(I1 i){  
        i.method1();  
    }  
    public void depend2(I3 i){  
        i.method4();  
    }  
    public void depend3(I3 i){  
        i.method5();  
    }  
}  
  
class D implements I1, I3{  
    public void method1() {  
        System.out.println("类D实现接口I1的方法1");  
    }  
    public void method4() {  
        System.out.println("类D实现接口I3的方法4");  
    }  
    public void method5() {  
        System.out.println("类D实现接口I3的方法5");  
    }  
} 

接口隔离原则的含义是:建立单一接口,不要建立庞大臃肿的接口,尽量细化接口,接口中的方法尽量少。也就是说,我们要为各个类建立专用的接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。本

迪米特法则

定义:一个对象应该对其他对象保持最少的了解。

说白点就是尽量降低耦合。
我在网上找到了这样一个简单例子,可以看一下,做个对比:
有一个集团公司,下属单位有分公司和直属部门,现在要求打印出所有下属单位的员工ID。先来看一下违反迪米特法则的设计。

//总公司员工  
class Employee{  
    private String id;  
    public void setId(String id){  
        this.id = id;  
    }  
    public String getId(){  
        return id;  
    }  
}  
  
//分公司员工  
class SubEmployee{  
    private String id;  
    public void setId(String id){  
        this.id = id;  
    }  
    public String getId(){  
        return id;  
    }  
}  
  
class SubCompanyManager{  
    public List<SubEmployee> getAllEmployee(){  
        List<SubEmployee> list = new ArrayList<SubEmployee>();  
        for(int i=0; i<100; i++){  
            SubEmployee emp = new SubEmployee();  
            //为分公司人员按顺序分配一个ID  
            emp.setId("分公司"+i);  
            list.add(emp);  
        }  
        return list;  
    }  
}  
  
class CompanyManager{  
  
    public List<Employee> getAllEmployee(){  
        List<Employee> list = new ArrayList<Employee>();  
        for(int i=0; i<30; i++){  
            Employee emp = new Employee();  
            //为总公司人员按顺序分配一个ID  
            emp.setId("总公司"+i);  
            list.add(emp);  
        }  
        return list;  
    }  
      
    public void printAllEmployee(SubCompanyManager sub){  
        List<SubEmployee> list1 = sub.getAllEmployee();  
        for(SubEmployee e:list1){  
            System.out.println(e.getId());  
        }  
  
        List<Employee> list2 = this.getAllEmployee();  
        for(Employee e:list2){  
            System.out.println(e.getId());  
        }  
    }  
}  
  
public class Client{  
    public static void main(String[] args){  
        CompanyManager e = new CompanyManager();  
        e.printAllEmployee(new SubCompanyManager());  
    }  
}  
    现在这个设计的主要问题出在CompanyManager中,根据迪米特法则,只与直接的朋友发生通信,而SubEmployee类并不是CompanyManager类的直接朋友(以局部变量出现的耦合不属于直接朋友),从逻辑上讲总公司只与他的分公司耦合就行了,与分公司的员工并没有任何联系,这样设计显然是增加了不必要的耦合。按照迪米特法则,应该避免类中出现这样非直接朋友关系的耦合。修改后的代码如下:
class SubCompanyManager{  
    public List<SubEmployee> getAllEmployee(){  
        List<SubEmployee> list = new ArrayList<SubEmployee>();  
        for(int i=0; i<100; i++){  
            SubEmployee emp = new SubEmployee();  
            //为分公司人员按顺序分配一个ID  
            emp.setId("分公司"+i);  
            list.add(emp);  
        }  
        return list;  
    }  
    public void printEmployee(){  
        List<SubEmployee> list = this.getAllEmployee();  
        for(SubEmployee e:list){  
            System.out.println(e.getId());  
        }  
    }  
}  
  
class CompanyManager{  
    public List<Employee> getAllEmployee(){  
        List<Employee> list = new ArrayList<Employee>();  
        for(int i=0; i<30; i++){  
            Employee emp = new Employee();  
            //为总公司人员按顺序分配一个ID  
            emp.setId("总公司"+i);  
            list.add(emp);  
        }  
        return list;  
    }  
      
    public void printAllEmployee(SubCompanyManager sub){  
        sub.printEmployee();  
        List<Employee> list2 = this.getAllEmployee();  
        for(Employee e:list2){  
            System.out.println(e.getId());  
        }  
    }  
}  

修改后,为分公司增加了打印人员ID的方法,总公司直接调用来打印,从而避免了与分公司的员工发生耦合。

开闭原则

这应该是我们听得最多的一个原则了,在平时的项目开发中,用的也最多,因为谁也不想,一次产品的迭代,还需要修改核心代码,然后全部重新测试一次。

定义:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。

这个定义没有那么复杂难理解,我不再做过多的解释,我这里还是通过一个小例子来说明:

public interface ICar {
    public String getName();
    public float getPrice();

}
public class Car implements ICar{

    private String name;
    private float price;
    public Car(String name,float price){
        this.name = name;
        this.price = price;
    }
    @Override
    public String getName() {
        return name;
    }

    @Override
    public float getPrice() {
        return price;
    }
}

当有一天我们获取车的价格需要打折时,可以重新写一个类SaleCar:

public class SaleCar extends Car{
    public SaleCar(String name, float price) {
        super(name, price);
    }

    @Override
    public float getPrice() {
        return super.getPrice()*8/10;
    }
}


我们这样做的目的是当有新功能出现的时候,尽量不要去修改原有的逻辑,可以实现一个新的类,然后覆写父类的方法,这样,原有的逻辑没有变,新的需求也实现了。当有一天出现bug了,可以直接修改这一个类就可以。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • 设计模式六大原则 设计模式六大原则(1):单一职责原则 定义:不要存在多于一个导致类变更的原因。通俗的说,即一个类...
    viva158阅读 763评论 0 1
  • 转载标注声明:http://www.uml.org.cn/sjms/201211023.asp 目录:[设计模式六...
    Bloo_m阅读 704评论 0 7
  • 设计模式六大原则(1):单一职责原则 定义:不要存在多于一个导致类变更的原因。通俗的说,即一个类只负责一项职责。 ...
    Jabir_Zhang阅读 639评论 0 3
  • 单一职责原则 (SRP) 全称 SRP , Single Responsibility Principle 单一职...
    米莉_L阅读 1,751评论 2 5
  • 设计模式之六大原则(转载) 关于设计模式的六大设计原则的资料网上很多...
    霄霄霄霄阅读 894评论 0 1