设计原则与设计模式

时间:2020-05-27
本文章向大家介绍设计原则与设计模式,主要包括设计原则与设计模式使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

1. 目录

2. 简介

本文是《深入浅出设计模式》的读书笔记、
下文说到的接口分为广义接口和狭义接口。
广义接口包括接口和抽象类。而狭义接口则单指编程语言中的接口(interface)。

2.1. 设计原则

2.1.1. 变化与不变化

找出应用中可能需要变化之处,把他们独立出来,不要和那些不需要变化的代码混合在一起。
把会变化的部分取出并封装起来,一边以后可以轻易地改动或扩充此部分,这样就不会影响到不需要改动的部分了。

2.1.2. 针对接口编程而不是针对实现编程

这里的接口是广义的接口。
面向接口编程是指设计类时,把实现的细节放在接口中,把一些实质动作,会改变的动作抽象成接口,然后在接口中实现,因为这些实现细节是很容易改变的有,因此我们要把他组件化,使其可复用。比如实现“小明在吃苹果”可以针对接口实现“{人接口}在{动作接口}{对象接口}”,然后在三个接口中分别实现小明类,吃动作类,苹果类。以后若还有类似需求,比如小明在切苹果。只要实现切动作类即可。具体的例子看下面。

比如《深入浅出设计模式》中第16页的例子。
要实现鸭子类的“叫”动作和“飞”动作。
如果是面向实现编程则简单的硬编码即可。
而如果面向接口编程则是,定义“叫动作”接口和“飞动作”接口,根据鸭子种类不同(绿头鸭嘎嘎叫,会飞,木头鸭不会叫不会飞,充气鸭吱吱叫不会飞)来组合,使代码可复用,不用一次次定义很多鸭子类。如下:

public class Duck{
    QuackBehavior qb; //叫动作接口成员
    FlyBehavior fb;   //飞动作接口成员
    public void quack(){  //叫动作函数
        qb.quack();
    }
    public void fly(){ //飞动作函数
        fb.fly();
    }
}

//实现叫动作接口示例
//木头鸭不会叫
public class SlientQuack implement QuackBehavior{
    public void quack(){
        system.out.printout("I can not quack");
    }
}

//创建木头鸭子类
public class WoolDuck extend Dcuk{
    public WoolDuck(){
        this.qb= new SlientQuack();
        this.fb = new CanNotFly();
        ....
    }
}

这样一来,在增加新鸭子类的时候,可以通过硬编码方式静态添加,也可以通过构造函数,传入接口动作参数动态生成新的鸭子类。
这样就把实现的细节委托给了接口对象。

2.1.3. 为了交互对象之间的松耦合设计而努力

这能简历有弹性的OO系统,能够应付变化,因为对象之间的相互依赖降到了最低。

2.1.4. 开放-关闭模式

对修改关闭,对扩展开放。

一个优秀的架构应该具有良好的扩展性,而对修改关闭,以避免修改后引入新bug和损坏依赖。
使用装饰器可以满足这个设计原则。扩展时先考虑组合,在考虑继承。

2.1.5. 依赖倒置原则

要依赖抽象而不是依赖具体类。
这个原则很像“面向接口编程”原则,在编程中,我们不能让高层组件直接依赖底层组件,不管高层底层,都应该依赖抽象。这个原则工厂模式总得到了体现。
依赖倒置中的倒置意思见下图:

将高层组件所依赖的组件抽象成抽象类或接口,而底层组件的调用也返回一个抽象类,或者实现这个抽象类。这样就能实现依赖倒置,并且解耦。
看下工厂模式的部分类图

工厂方法和具体产品都依赖产品这个抽象类或接口,也就是解耦合,我们可以不修改代码,就能添加新的子产品类,只要这个类实现了产品类接口。

2.1.6. 单一职责

一个类应该只有一个引起变化的原因。

3. 观察者模式

在Subject中有一个成员变量是Observer类型的数组,里面包含着所有“观察”着Subject的观察者。所以两个接口之间是1对N的聚合关系。

而ConcreteSub和ConcreteObserver是一个动态关联的关系,因为ConcreteObserver的update方法中有Subject类的参数,以此来分辨是哪个ConcreteSubject发来的更新操作。(一个观察者可以观察多个对象)。

3.1. 通知的模式

通知的模式分为push和pull,通常来说push模式更广泛一点,pull会因为多个观察者频发送request而造成网络拥堵。
但pull也有特点,可以让观察者自定义因素询问操作,比如在subject内声明一个changed变量,subject变化不大时,其值为false,只有当observer对象pull时,且change为真,subject才会发出回应,从而使得observer真正执行update操作。

4. 装饰者模式

有时,“继承”这个OOP特性会被滥用,用于构建出一个个和而不同的类,而装饰者模式则可以有效地解决“继承滥用”的问题。装饰者模式使用了“组合”而不是“继承”。
它动态地给一个对象添加职责,以区分和其他类的不同。

通过装饰者模式,还可以解决开放-关闭原则问题,因为装饰者模式并没有修改源代码,而是扩展原来的类。

在Decorator类对象中,会有一个成员变量指向被装饰的对象。这与适配器模式和代理模式相似。
与适配器模式相比较,适配器模式是更改接口,以对接另一个系统或对象。而装饰器模式不改变接口且添加新职责。
与代理模式相比较,装饰器模式更强调“装饰”或“增强”原来的类,而代理模式强调对原来的类做“权限管理”或“访问控制”等,而且做这些“新功能”的主体是代理类。本质来说就是意图不一样。

5. 适配器模式

适配器将一个接口转成另一个接口,使接口不兼容的那些类可以一起工作。

Adaptee本身并不满足Target接口的要求,所以要借助Adapter(适配器)。像Decoratot一样,在Adapter内也维护这一个成员变量指向原来的对象。

调用Adapter的request其实就是调用Adaptee的specilRequest()。

适配器又分为对象适配器和类适配器。
如果要使用类适配器的话,则要求编程语言支持多继承,或者Target是一个狭义接口Adaptee是个类才行。
而对象适配器则只需要实现Target这个广义接口既可以。

6. 代理模式

proxy对象持有对realSubject的引用,所以在必要的时候可以请求发给realSubject。比如,在使用虚拟代理时,对象已经被创建/加载出来了,这个时候就没必要用虚拟代理回应了。使用保护代理时,访问者有足够的权限,就没必要拒绝访问了。
代理模式分为以下三个用途:

  • 远程代理,控制访问对象。
  • 虚拟代理,控制访问创建开销大的资源,lazy load。
  • 保护代理,基于权限控制对资源的访问。

虚拟代理就比如加载网站时,对一些大资源图片等,加载未完成时,浏览器会使用虚拟代理类先给页面返回“加载中,请稍后”等类似图片,一旦大资源被加载了就立即替换。
代理模式和装饰模式都有“修饰”原有对象的作用。两者的区别可以在看下装饰器小节中的总结。总之就是代理模式和装饰模式的意图不一样,代理是用代理类来委托工作,而装饰是为了增强原先的类。

原文地址:https://www.cnblogs.com/Jun10ng/p/12975196.html