常用的几种设计模式(一):设计原则、策略模式和观察者模式

核心:复用----抵御变化

面向对象的设计原则:

1、依赖倒置原则(DIP)

  • 高层模块(稳定)不应该依赖于低层模块(变化),二者都应该依赖 于抽象(稳定) 。

  • 抽象(稳定)不应该依赖于实现细节(变化) ,实现细节应该依赖于 抽象(稳定)。

2、开放封闭原则(OCP)

  • 对扩展开放,对更改封闭。

  • 类模块应该是可扩展的,但是不可修改。

3、单一职责原则(SRP)

  • 一个类应该仅有一个引起它变化的原因。

  • 变化的方向隐含着类的责任。

4、Liskov 替换原则(LSP)

  • 子类必须能够替换它们的基类(IS-A)。

  • 继承表达类型抽象

5、接口隔离原则(ISP)

  • 不应该强迫客户程序依赖它们不用的方法。

  • 接口应该小而完备。

6、优先使用对象组合,而不是类继承

  • 类继承通常为“白箱复用”,对象组合通常为“黑箱复用”。

  • 继承在某种程度上破坏了封装性,子类父类耦合度高。(对父类的方法重写,优先调用子类)

  • 而对象组合则只要求被组合的对象具有良好定义的接口,耦合 度低。

  • 推荐文章:https://zhuanlan.zhihu.com/p/60282972

7、封装变化点

  • 使用封装来创建对象之间的分界层,让设计者可以在分界层的 一侧进行修改,而不会对另一侧产生不良的影响,从而实现层 次间的松耦合

8、针对接口编程,而不是针对实现编程

  • 不将变量类型声明为某个特定的具体类,而是声明为某个接口。

  • 客户程序无需获知对象的具体类型,只需要知道对象所具有的 接口。

  • 减少系统中各部分的依赖关系,从而实现“高内聚、松耦合” 的类型设计方案。

1、策略模式

1.1、使用场景:

1、如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。

2、一个系统需要动态地在几种算法中选择一种。

3、如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。

4、解决大量if/else、switch的使用

1.2、实现思路

将要被多次重复使用,且可能还存在拓展的可能性的行为方法,抽象成为一个接口(行为方法接口),子类去实现这个接口并实现里面的方法,再新建一个管理类,通过管理类去调用行为方法。

例如:鸟要吃饭,猪要吃饭,猫要吃饭

新建一个行为接口,里面放一个吃饭的抽象方法,鸟、猪、猫都去实现吃饭的这个方法,再新建一个喂食类(class),通过喂食类去调用吃饭的方法,管理员在想给鸟喂食的时候,只需要new 喂食类(new 鸟类),就能实现给鸟喂食

1.3、实现代码

package gof;
​
/**
 * @author yishuai
 * @description
 * 策略模式:
 * 将要被多次重复使用,且可能还存在拓展的可能性的行为方法,
 * 抽象成为一个接口(行为方法接口),子类去实现这个接口并实现里面的方法,再新建一个管理类,通过管理类去调用行为方法。
 * @date 2021/4/11 4:43 下午
 */
public class StrategyModel {
    public static void main(String[] args) {
        //需要喂食谁,就改manger传递的类
        Manger manger = new Manger(new Bird());
        manger.feed();
    }
}
​
/**
 * 管理所有动物吃饭的类
 */
class Manger{
    private Action action;
​
    public Manger(Action action) {
        this.action = action;
    }
​
    /**
     * 喂食方法
     */
    void feed(){
        action.eat();
    }
}
​
/**
 * 行为接口
 */
interface Action{
    void eat();
}
​
/**
 * 鸟类
 */
class Bird implements Action{
​
    @Override
    public void eat() {
        System.out.println("鸟开始吃饭了~");
    }
}
​
/**
 * 猪类
 */
class Pig implements Action{
​
    @Override
    public void eat() {
        System.out.println("猪开始吃饭了~");
    }
}
​
/**
 * 猫类
 */
class Cat implements Action{
​
    @Override
    public void eat() {
        System.out.println("猫开始吃饭了~");
    }
}

1.4、优点与不足

  • 优点:可以动态的增加行为,不需要改动原有的代码,降低了风险度和耦合度

    • 喂养动物不会只局限于鸟、猪、猫,以后如果增加狗、蛇等动物,不需要修改原有的代码,只需要增加相应的狗、蛇类,在调用manger方法到时候,传递狗、蛇的数据类型即可

  • 缺点:多了很多类,占内存

2、观察者模式

2.1、使用场景

消息的订阅与推送

2.2、实现思路

假设有一个明星的QQ粉丝群,群里面有粉丝,群全员禁言,只有明星可以发言

  • Subject:抽象明星(抽象被观察者),抽象明星角色把所有粉丝(观察者)保存在一个群(集合)里,每个群都可以有任意数量的粉丝(观察者),抽象明星提供一个接口,可以增加和删除粉丝(观察者)。

  • ConcreteSubject:具体的明星管理员(具体被观察者),该明星管理员将明星的状态告诉这些粉丝(具体观察者对象),在明星的状态发生改变时,给所有粉丝(注册过的观察者)发送通知。

  • Observer:粉丝(抽象观察者),是粉丝(观察者)的抽象类,它定义了一个更新接口,使得在得到明星状态变更通知时,更新自己脑海里的信息。

  • ConcrereObserver:具体的粉丝(具体观察者),实现抽象粉丝(抽象观察者)定义的更新接口,以便在得到明星状态更改时,更新自己脑海里的信息

2.3、实现代码

package gof;
​
import java.util.ArrayList;
import java.util.List;
​
/**
 * @author yishuai
 * @description
 *
 * 观察者模式
 * Subject:抽象主题(抽象被观察者),抽象主题角色把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者,
 *          抽象主题提供一个接口,可以增加和删除观察者对象。
 * ConcreteSubject:具体主题(具体被观察者),该角色将有关状态存入具体观察者对象,
 *          在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
 * Observer:抽象观察者,是观察者者的抽象类,它定义了一个更新接口,使得在得到主题更改通知时更新自己。
 * ConcrereObserver:具体观察者,实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态
 * @date 2021/4/11 6:46 下午
 */
public class ObserveModel {
    public static void main(String[] args) {
        //新建一个具体的明星(被观察者)
        Cxk cxk = new Cxk();
        RealFans xm = new RealFans("小名");
        RealFans xh = new RealFans("小红");
        RealFans xj = new RealFans("小军");
        cxk.addFans(xm);
        cxk.addFans(xh);
        cxk.addFans(xj);
​
        //给所有的粉丝发消息
        cxk.notifyFans("我已经练习两年半啦~");
    }
​
}
​
/**
 * Subject
 * 明星抽象类(抽象被观察者)
 */
interface Star{
    /**
     * 新增粉丝(观察者)
     * @param fans 具体的粉丝(观察者)
     */
    void addFans(Fans fans);
    /**
     * 删除粉丝(观察者)
     * @param fans 具体的粉丝(观察者)
     */
    void delFans(Fans fans);
    /**
     * 通知粉丝(观察者)
     * @param message 更新的消息内容
     */
    void notifyFans(String message);
}
​
/**
 * ConcreteSubject
 * 具体的明星
 */
class Cxk implements Star{
​
    //粉丝群
    private List<Fans> fenses = new ArrayList<>();
​
    @Override
    public void addFans(Fans fans) {
        fenses.add(fans);
    }
​
    @Override
    public void delFans(Fans fans) {
        fenses.remove(fans);
    }
​
    @Override
    public void notifyFans(String message) {
        for (Fans fans : fenses) {
            //通知所有的粉丝更新
            fans.update(message);
        }
    }
}
​
/**
 * Observer
 * 抽象粉丝(抽象观察者)
 */
interface Fans{
    /**
     * 更新信息
     * @param message 改变的信息
     */
    void update(String message);
}
​
/**
 * ConcrereObserver
 * 具体的粉丝(具体的观察者)
 */
class RealFans implements Fans{
    //粉丝的名字
    private String name;
​
    /**
     * 通过构造器,新建粉丝的时候传入粉丝名字
     * @param name 粉丝的名字
     */
    public RealFans(String name) {
        this.name = name;
    }
​
    @Override
    public void update(String message) {
        System.out.println(name+"收到消息:"+message);
    }
}

2.4、优点与不足

  • 优点

    • 观察者和被观察者都依赖于抽象接口,解耦

    • 支持广播通讯,一对多进行通讯

  • 缺点

    • 因为要一个一个通知,所以特别费时间

    • 中间一个观察者出问题,后面的都无法收到通知

    • 只能监听到被观察者的变化,不知道被观察者怎么变化的