Android开发

六大设计原则(设计模式之禅读书笔记)

2017-09-07  本文已影响23人  古都旧城

[TOC]

单一设计原则---(专人专事)

单一职责原则的定义是:应该有且仅有一个原因引起类的变更。

image.png

优化:
重新拆封成两个接口,IUserBO负责用户的属性,简单地说,IUserBO的职责就是收集和 反馈用户的属性信息;IUserBiz负责用户的行为,完成用户信息的维护和变更。

image.png image.png

简单理解就是 get set 一起,其他操作封装成biz(biz是Business的缩写,实际上就是控制层(业务逻辑层)),当然不局限于这种类型的对象。

注意 单一职责原则提出了一个编写程序的标准,用“职责”或“变化原因”来衡量接口或 类设计得是否优良,但是“职责”和“变化原因”都是不可度量的,因项目而异,因环境而异

单一职责适用于接口、类,同时也适用于方法,什么意思呢?一个方法尽可能做一件事 情,比如一个方法修改用户密码,不要把这个方法放到“修改用户信息”方法中,这个方法的 颗粒度很粗。
如果要修改用户名称,就调用changeUserName方法;要修改家庭地址, 就调用changeHomeAddress方法;要修改单位电话,就调用changeOfficeTel方法。每个方法的 职责非常清晰明确,不仅开发简单,而且日后的维护也非常容易,大家可以逐渐养成这样的 习惯。

这个单一原则重在理解,不能认死理,拆分太严重也会导致类或者方法数过多,具体情况具体分析吧。

里氏替换原则---(继承规范)

主要是为良好的继承定义了一个规范。

这个规范就是:所有引用基类的地方必须能透明地使用其子类的对象(不会改变任何逻辑)
通俗点讲,只要父类能出现的地方子类就可以出现,而且 替换为子类也不会产生任何错误或异常,使用者可能根本就不需要知道是父类还是子类。但 是,反过来就不行了,有子类出现的地方,父类未必就能适应。
更正宗的定义:如果对每一个类型为S的对象o1,都有类型为T的对 象o2,使得以T定义的所有程序P在所有的对象o1都代换成o2时,程序P的行为没有发生变 化,那么类型S是类型T的子类型。

说明:这是给继承定义的一种良好的规范,现实中可能会出现不符合这种原则的代码,所以这是规范,并不是所有都是这样的。

细分里氏替换原则四种含义:

public class Father {
 public Collection doSomething(HashMap map){
   System.out.println("父类被执行...");
   return map.values();
 }
}
public class Son extends Father {
  //放大输入参数类型
  public Collection doSomething(Map map){
    System.out.println("子类被执行...");
    return map.values();
  }
}

请注意粗体部分,与父类的方法名相同,但又不是覆写(Override)父类的方法。你加 个@Override试试看,会报错的,为什么呢?方法名虽然相同,但方法的输入参数不同,就 不是覆写,那这是什么呢?是重载(Overload)!不用大惊小怪的,不在一个类就不能是重 载了?继承是什么意思,子类拥有父类的所有属性和方法,方法名相同,输入参数类型又不 相同,当然是重载了。

父类使用场景:

public class Client {
   public static void invoker(){
      //父类存在的地方,子类就应该能够存在
      Father f = new Father();
      HashMap map = new HashMap();
      f.doSomething(map);
   }
   public static void main(String[] args) {
      invoker();
   }
}

运行结果:父类被执行...
根据里氏替换原则在这里使用子类替换:

public class Client {
   public static void invoker(){
      //父类存在的地方,子类就应该能够存在
      Son f =new Son();
      HashMap map = new HashMap();
      f.doSomething(map);
   }
   public static void main(String[] args) {
      invoker();
   }
}

运行结果:父类被执行...
运行结果还是一样,看明白是怎么回事了吗?父类方法的输入参数是HashMap类型,子 类的输入参数是Map类型,也就是说子类的输入参数类型的范围扩大了,子类代替父类传递 到调用者中,子类的方法永远都不会被执行。这是正确的,如果你想让子类的方法运行,就 必须覆写父类的方法。

对调参数:

public class Father {
 public Collection doSomething(Map map){
   System.out.println("父类被执行...");
   return map.values();
 }
}
public class Son extends Father {
  //放大输入参数类型
  public Collection doSomething(HashMap map){
    System.out.println("子类被执行...");
    return map.values();
  }
}

如果依然使用上面的使用场景运行结果就会变成这样:
运行结果:父类被执行...
更换子类
运行结果:子类被执行...

这就不正常了,子类在没有覆写父类的方法的前提下,子类方法被执行了,这会引起业务 逻辑混乱 ,“歪曲”了父类的意图,引起一堆意想不到的业务逻辑混乱,所以子类中方法的前置条 件必须与超类中被覆写的方法的前置条件相同或者更宽松。

综合3、4条可以总结一句话:子类入参类型可以放大范围(可以是父入参的父类),输出结果要缩小范围(可是父出参的子类)。
理解起来比较困难。

依赖倒置原则---(面向接口编程)

举个例子:
司机开动奔驰车:

image.png

这样设计的话突然来个宝马,司机没有对应开宝马的方法,就不能执行了。
司机类和奔驰车类之间是紧耦合的关系,其导致的结果就是系统的可维护性大大降低。

对上面的例子进行优化,引入依赖倒置 原则后的类图如图3-2所示

image.png

建立两个接口:IDriver和ICar,分别定义了司机和汽车的各个职能,司机就是驾驶汽 车,必须实现drive()方法

在业务场景中,我们贯彻“抽象不应该依赖细节”,也就是我们认为抽象(ICar接口)不 依赖BMW和Benz两个实现类(细节),因此在高层次的模块中应用都是抽象
代码如下:

    public interface ICar {
        //是汽车就应该能跑
        public void run();
    }
    public class Benz implements ICar{
        //汽车肯定会跑
        public void run(){
            System.out.println("奔驰汽车开始运行...");
        }
    }
    public class BMW implements ICar{
        //宝马车当然也可以开动了
        public void run(){
            System.out.println("宝马汽车开始运行...");
        }
    }
  public class Driver implements IDriver{
        //司机的主要职责就是驾驶汽车
        public void drive(ICar car){
            car.run();
        }
    }

讲了这么多,估计大家对“倒置”这个词还是有点不理解,那到底什么是“倒置”呢?我们 先说“正置”是什么意思,依赖正置就是类间的依赖是实实在在的实现类间的依赖,也就是面 向实现编程,这也是正常人的思维方式,我要开奔驰车就依赖奔驰车,我要使用笔记本电脑 就直接依赖笔记本电脑,而编写程序需要的是对现实世界的事物进行抽象,抽象的结果就是 有了抽象类和接口,然后我们根据系统设计的需要产生了抽象间的依赖,代替了人们传统思 维中的事物间的依赖,“倒置”就是从这里产生的。

接口隔离原则---(定义接口规范)

  1. 客户端不应该依 赖它不需要的接口

依赖它需要的接口,客 户端需要什么接口就提供什么接口,把不需要的接口剔除掉,那就需要对接口进行细化,保 证其纯洁性

  1. 类间的依赖关系应该建立在最小的接口上

它要求是最小 的接口,也是要求接口细化,接口纯洁,与第一个定义如出一辙,只是一个事物的两种不同 描述。

总结上面两句话:

实际应用上面就是一个接口里面不要写太多方法,如果确实需要很多方法的话,应该尽量根据实际需求进行拆分,拆分成多个接口,按需实现。

例如:对美女的定义 面貌、身材和气质 定义了如下接口

image.png

然而每个人的审美观不同,并不是所有的人都认为美女都是这三种条件的
比如唐朝 身材就认为胖点的好
所以接口要进行拆分,按需进行实现

image.png

接口是我们设计时对外 提供的契约,通过分散定义多个接口,可以预防未来变更的扩散,提高系统的灵活性和可维 护性。

根据接口隔离原则拆分接口时,首先必须满足单一职责原则。

迪米特法则---(低耦合)

对类的低耦合提出了明确的要求

  1. 只和朋友交流
    老师想让体育委员确认一下全班女生来齐没有,就对他 说:“你去把全班女生清一下。
image.png image.png

场景类:

image.png

首先确定Teacher类有几个朋友类,它仅有一个朋友类—— GroupLeader。为什么Girl不是朋友类呢?Teacher也对它产生了依赖关系呀!朋友类的定义是 这样的:出现在成员变量、方法的输入输出参数中的类称为成员朋友类,而出现在方法体内 部的类不属于朋友类,而Girl这个类就是出现在commond方法体内,因此不属于Teacher类的 朋友类。迪米特法则告诉我们一个类只和朋友类交流,但是我们刚刚定义的commond方法却 与Girl类有了交流,声明了一个List动态数组,也就是与一个陌生的类Girl有了交流, 这样就破坏了Teacher的健壮性。方法是类的一个行为,类竟然不知道自己的行为与其他类 产生依赖关系,这是不允许的,严重违反了迪米特法则。

所以应该修改调整一下:

image.png image.png

场景类:

image.png

对程序进行了简单的修改,把Teacher中对List的初始化移动到了场景类中,同时 在GroupLeader中增加了对Girl的注入,避开了Teacher类对陌生类Girl的访问,降低了系统间 的耦合,提高了系统的健壮性。

比如说把大象装冰箱需要三步,任何一步失败都会导致接下来的动作无法执行,我们应该封装一个把大象装冰箱的方法(涵盖这三步)开放出去,而不应该把这三步都开放出去。

开闭原则---(开放扩展,关闭修改)

开闭原则的定义已经非常明确地告诉我们:软件实体应该对扩展开放,对修改关闭,其 含义是说一个软件实体应该通过扩展来实现变化,而不是通过修改已有的代码来实现变化

上一篇下一篇

猜你喜欢

热点阅读