常用设计模式&设计原则

1 设计模式

1 策略模式

策略模式:
为某个可以变化的功能,将其设计成一个接口
为该接口扩展出一系列实现类,在生成该对象时再赋予该对象特有的特征
让功能的实现与对象脱离,将对象和行为分离,以降低耦合,提高代码复用

如模拟一个鸭子院,有绿头鸭,红头鸭
起初设计每个鸭子都有各自的成员和方法,但随着鸭子数量的增多
需要维护设计太多类,为此可以考虑使用继承创建一个鸭子父类
所有的鸭子子类来继承它,并选择性重写父类中的方法

但是随着系统逐渐臃肿时,当需要更改父类中的一个方法时
成千上万个子类中的方法也需要被重写,继承牵一发而动全身

为此可以将飞行,鸣叫等行为做成接口,为这些接口做各种实现类
父类将这些接口作为成员,子类选择性的实例化这些接口的实现类,以完成不同的功能
并可以通过set方法来替换功能

2 观察者模式

观察者模式:
将系统中的角色分成了主题和观察者,观察者依附主题而工作,主题可以发布数据让所有观察者得到同一份数据,以得到更干净的设计,Swing和许多GUI框架都大量使用了观察者模式

参照报社的工作模式,观察者模式就等于 出版社+订阅者
出版社管理所有的订阅者,每当需要发布消息时,向它的所有订阅者发布信息

可以设计两个接口来完成观察者模式
ISpeakerIListenerISpeaker作为主题,IListener作为观察者
ISpeaker的抽象方法有 addOneListener,removeOneListener,speakMessage
IListener的抽象方法有 readMessage
作为主题的类实现了ISpeaker接口后 再维护一个List<IListener>
每当发布信息speakMessage时,轮询List调用所有观察者的readMessage即可

主题不知道观察者的任何信息,它不在于观察者到底是什么,到底在干什么
观察者不知道主题的具体实现,它只在于来自主题的消息,这就是松耦合

上述由主题向观察者发布消息的行为可以看作主题向观察者的推送
同时也可以直接将主题对象发给观察者,由观察者自己get主题里的数据,来主动拉取信息

3 装饰者模式

装饰者模式:
被装饰者和装饰者属于同一类型,将被装饰者作为装饰者的成员,装饰的过程就是把被装饰者作为装饰者的参数,生成新的装饰者的过程,把对象作为成员通过组合来扩展功能,Java.io种就充满了装饰者模式

当设计一家咖啡厅菜单时,起初可以为每种饮品设计一个类
但是随着饮品的增多,需要的类也越来越多,且还伴随着大杯,小杯,加糖,加奶等需求
如果为每种需求都设计一个类,如大杯加糖咖啡,小杯加糖加奶拿铁
那么系统中就会出现"类爆炸",需要维护太多的类

为此可以采用装饰者模式,将基础的饮品作为"被装饰者",将大杯,加糖等作为"装饰者"
它们有共同的父类"材料",将被装饰者作为装饰者的成员
当需要小杯加糖加奶拿铁时,先创建一个拿铁对象
再将其作为参数使用小杯"装饰",将得到的对象再使用加糖装饰...

示例:

public class Tea extends Beverage {

    public Tea() { super("Tea"); }

    @Override
    public double cost() { return 10; }
}

public class Milk extends Decorator {
    private Beverage beverage;

    public Milk(Beverage beverage) {
        super("牛奶");
        this.beverage = beverage;
    }

    @Override
    public double cost() {
        return 3 + beverage.cost();
    }

    @Override
    public String getDescription() {
        return beverage.getDescription() + " + Milk";
    }
}

eg:
Tea tea = new Tea();
tea = new Milk(tea); // 被牛奶装饰
tea = new Mocha(tea); // 被摩卡装饰
tea = new BigCup(tea); // 被大杯装饰
获得了加奶加摩卡大杯茶

java.io中的装饰者模式:
在这里插入图片描述

FileInputStream inputStream = new FileInputStream("");
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);

4 工厂模式

工厂模式:
当在一个类中创建了大量的对象,对象越多这个类越脆弱,稍微有一个依赖进行了修改,整个类就无法正常工作,为此需要把功能实现和对象的创建分离,将对象的创建过程封装起来,使程序解耦,将功能实现和创建对象分离,以降低依赖

工厂模式可以分为简单工厂,工厂方法

简单工厂提供一个生产对应对象的方法或静态方法,返回值为该类对象
将对象的创建过程放到了另一个类的方法中
public static Pizza createPizza(String type) {
    Pizza pizza = null;

    if("cheese".equals(type)) {
        pizza = new CheesePizza();
    } else if("clam".equals(type)) {
        pizza = new ClamPizza();
    } else {
        return null;
    }
    
    return pizza;
}


工厂方法定义一个创建对象的抽象类,把创建对象的过程推迟到了子类
当子类试图从工厂中获取对象时,具体生产对象是由子类中完成的抽象方法实现的
public abstract class PizzaStore {

    public Pizza getPizza(String type) {
        Pizza pizza;

        pizza = createPizza(type);
        return pizza;
    }

    protected abstract Pizza createPizza(String type);
}

5 单例模式

单例模式:
有时候某个对象整个应用中只需要一个,如注册中心,日志,线程池,如果这些对象不加以约束的随意创建,就会导致程序的行为异常,出现预期之外的结果,单例模式确保一个类只有一个实例,提供一个全局访问点

单例模式常常用来管理共享唯一的资源

普通单例模式:
public class Registry {
    private static Registry instance;

    private Registry() {}
    
    public static Registry getInstance() {
        if(instance == null) {
            instance = new Registry();
        }
        return instance;
    }     
}


多线程下同步单例模式:
public class Registry {
    private volatile static Registry registry;

    private Registry() {}

    public static Registry getInstance() {
        if(registry == null) {
            synchronized (Registry.class) {
                if(registry == null) {
                    return new Registry();
                }
            }
        }
        return registry;
    }
}

上面两种单例的创建方式都采用了懒汉模式
即需要获取时再生产,饿汉模式也可以
但如果有大量由饿汉模式创建的对象,当应用启动时,会带给用户不太好的体验

6 命令模式

命令模式:
通过命令模式,可以将方法封装起来,将动作封装成命令对象,这样就可以随意存储,传递和调用它们,命令模式将发出请求的对象和执行请求的对象解耦

在设计一个遥控器的过程中,遥控器可以控制电视,台灯的开关
不采用设计模式的情况下,将电视和台灯等对象封装到遥控器对象中
遥控器的开关方法包装着电视,台灯的开关方法
这样的设计首先造成了依赖,功能又与对象耦合到了一起

如果采用命令模式,设计一个Command接口,该接口内存在一个抽象方法execute()
为该Command接口实现不同的命令对象,如开灯对象,关灯对象
开灯对象和关灯对象中封装着具体的对象Light,由命令对象来完成具体的功能实现
将该接口采用组合作为调用者(遥控器)的成员,通过set来替换命令对象

当调用者进行某此调用时,调用的是命令对象中的方法
而命令对象将来完成具体的操作,以此将发出请求的对象和执行请求的对象解耦

在这里插入图片描述

7 适配器模式

适配器模式:
将一个类的接口,转换成期望的另一个接口
适配器模式让原本不兼容的类可以轻松合作

当需要一个接口实现多种功能时,采用适配器模式
在JDK的swing技术中,通常要为各种组件增加侦听器,如焦点的获取/失去
	/*编号文本框获取焦点,编号文本框被全部选中*/
        this.jtxtId.addFocusListener(new FocusAdapter() {
            @Override
            public void focusGained(FocusEvent e) {
                jtxtId.selectAll();
            }
        });

        /*名称文本框获取焦点,名称文本框被全部选中*/
        this.jtxtName.addFocusListener(new FocusAdapter() {
            @Override
            public void focusGained(FocusEvent e) {
                jtxtName.selectAll();
            }
        });

FocusAdapter就是一个适配器,它实现了FocusListener接口
这里将具体的实现方式交给用户,让用户重写其中的方法来根据场景使用

通过适配器模式可以将功能与实现解耦,通过组合可以替换不同的适配器
且适配器可以为空,由用户来自己实现

在这里插入图片描述

8 模板方法模式

模板方法模式:
在一个方法中定义了一个算法的骨架,将一些步骤延迟到子类中,模板方法可以使得子类在不更改算法框架的结构下,重新定义算法中的某些步骤

即父类中有一个包含了若干抽象方法的模板方法,不同的子类继承父类实现不同的抽象方法
模板方法就有了不同的功能

在制作咖啡和茶的过程中
茶: 1,把水煮沸 -> 2,用沸水冲泡茶叶 -> 3,把茶倒进杯子里 -> 4,加柠檬
咖啡: 1,把水煮沸 -> 2,用沸水冲泡咖啡 -> 3,把咖啡倒进杯子里 -> 4,加糖和牛奶
我们可以简单的把这每个步骤做成一个方法放在具体的TeaCoffee类中
但这样又造成了功能与实现耦合,且发现了第一步和第三步是一样的,为此可以采用模板方法模式

public abstract class Drink {

    public Drink() {}

    public final void prepare() {
        boilWater();
        brew();
        pourInCup();
        add();
    }

    public abstract void brew();

    public abstract void add();

    public void boilWater() {
        System.out.println("把水煮沸");
    }

    public void pourInCup() {
        System.out.println("倒进杯子里");
    }
}

public class Tea extends Drink {

    public Tea() {}

    @Override
    public void brew() { System.out.println("用沸水冲泡茶叶"); }

    @Override
    public void add() { System.out.println("加柠檬"); }
}
当调用Tea中的prepare()方法时,其实是调用了Drink类中的prepare()方法
此时prepare()方法就是一个模板方法,会依次执行boilWater() brew() pourInCup() add()
由于是Tea调用的prepare() 所以模板方法中的抽象方法选择了Tea中的brew()add()
将部分步骤延迟到了子类,使得功能与实现解耦

同时还可以在模板方法中增加hook() 来控制模板方法的流程 如:
public abstract class Drink {

    public Drink() {}

    public final void prepare() {
        boilWater();
        brew();
        pourInCup();
        //挂钩点
        if(wantAddSomething()) {
            add();
        }
    }

    public abstract void brew();

    public abstract void add();

    public void boilWater() { System.out.println("把水煮沸"); }

    public void pourInCup() { System.out.println("倒进杯子里"); }

    //hook,可以被子类覆盖,从而对模板方法进行一定的控制
    public boolean wantAddSomething() {
        return true;
    }
}
子类可以重写hook(),即上述的wantAddSomething()来控制模板方法的流程
hook()能影响模板方法的流程,使子类有一定能力控制模板方法的走向

在这里插入图片描述
在java.util中的Arrays.sort()方法可以进行排序,对于简单的Integer类型数组可以直接排序,但也可以让某个类实现Comparable接口中的compareTo()来指定自己想要的排序条件,这个过程中,sort()就可以看作模板方法,compareTo()可以看作模板方法中的一环,由用户实现来更改最终模板方法执行的结果

9 状态模式

状态模式:
允许对象在内部状态改变时改变它的行为

考虑在设计一个糖果机时,糖果机有不同的状态
如未投币,已投币,出货中,退款中等状态
如果不采用设计模式,需要在多个方法中不断进行if-else的判断当前的状态,如:
public void insertMoney() {
        if(this.state == NO_MONEY) {
            System.out.println("已投币");
            this.state = HAS_MONEY;
            System.out.println("请点击 <购买> 或 <退款> 按钮");
            return;
        } else if(this.state == HAS_MONEY) {
            this.state = BACK_MONEY;
            System.out.println("请不要连续投币,马上会退出您的硬币");
            backMoney();
            return;
        }
    }

虽然能够简单完成,但是一旦增加了新的需求,如中奖需求
整个机器的代码大部分都要重写,先前的做法违反了开闭原则,并且系统没有弹性,逻辑混乱

现在采用状态模式来设计,定义一个State接口,糖果机的每种状态都来实现这个State接口
将糖果机的动作委托到状态类中
public class Machine {
    private State soldOutState;
    private State noMoneyState;
    private State hasMoneyState;
    private State soldState;

    private State state;
    private int count = 0;

    public Machine(int count) {
        this.soldOutState = new SoldOutState(this);
        this.noMoneyState = new NoMoneyState(this);
        this.hasMoneyState = new HasMoneyState(this);
        this.soldState = new SoldState(this);

        this.count = count;
        if(count > 0) {
            this.state = noMoneyState;
        }
}
   
// 状态接口
public interface State {
    //投币
    void insertMoney();

    //退款
    void ejectMoney();

    //转动曲柄
    void turnCrank();

    //分发糖果
    void dispense();
}

// 没有钱投入机器时的糖果机状态
public class NoMoneyState implements State {
    private Machine machine;

    public NoMoneyState(Machine machine) {
        this.machine = machine;
    }

    @Override
    public void insertMoney() {
        System.out.println("你投入了一枚硬币");
        machine.setState(machine.getHasMoneyState());
    }

    @Override
    public void ejectMoney() {
        System.out.println("尚未投币,无法退款");
    }

    @Override
    public void turnCrank() {
        System.out.println("尚未投币,无法转动曲柄");
    }

    @Override
    public void dispense() {
        System.out.println("尚未投币,无法获取糖果");
    }

}

这是一个双向组合的过程,各种状态类被组合到了糖果机中,糖果机被组合到了状态类中
糖果机的各种行为是状态类来执行的,状态类工作后会对糖果机进行调用,以进入下一个状态

在这里插入图片描述

10 代理模式

代理模式:
为一个对象提供一个"替身",以控制对该对象的访问

代理模式为真实对象提供替身,当调用代理中的方法时
代理对象会委托真实对象来进行处理,但在委托的前后
可以增加更多的功能,如权限检测,安全判断,远程调用...
从而非侵入式地扩展了真实对象的功能并保护真实对象


代理的应用场景很多,如
防火墙代理: 控制网络资源的访问,避免非法操作
缓存代理: 为开销大的运算结果提供暂时存储,允许多个客户共享结果,以减少计算
同步代理: 多线程环境下,为真实对象提供安全的访问
...

在这里插入图片描述

2 设计原则

2.1 区分变与不变

让系统的某部分改变不会影响其它部分,以使得代码变化引起的更改减少,让系统更有弹性

找出应用中可能需要变化的地方,将它们独立出来
不要和那些不需要变化的代码放在一起

2.2 多用组合,少用继承

尽可能少地使用继承,将接口当作成员以扩展类的功能

继承虽能获取父类的代码,以完成代码复用
但是继承同时压缩了子类变化的空间,并且父类如果要做更改
那么所有子类都会被殃及

2.3 松耦合

两个对象之间松耦合,它们仍然可以交互,只是不清楚彼此的细节

松耦合能让两个对象不清楚,不在于彼此的具体实现
还能够完成数据的交互,能够提高系统的弹性

2.4 开闭原则

类应该对扩展开发,对修改关闭

程序应该免于变化且善于扩展
在尽量不修改源码的基础上,就可以搭配新的行为

2.5 依赖倒置原则

当直接实例化一个对象时,就是在依赖它的具体实现类,要尽量减少这种依赖

不要让高层组件依赖底层组件,且不管高层还是底层组件都应该依赖于"抽象"
父类和接口就是抽象

如在披萨店中,如果让PizzaStore直接依赖各种CheesePizzaBeefPizza就是高层组件依赖底层组件
整个PizzaStore大量的依赖实现类,为此可以让PizzaStore依赖父类Pizza,让各种披萨作为Pizza的子类
以遵循依赖倒置原则,将功能与创建对象分离

2.6 最少知识原则

不要让太多的类耦合在一起

不要让太多类耦合在一起,免得修改系统中的一部分,影响到其它部分
如果许多类之间相互依赖,那么这个系统就是一个易碎的系统,需要更多精力去维护

2.7 好莱坞原则

别调用我,我会调用你

好莱坞原则可以降低对象之间的依赖
如在一个糟糕的系统中,高层组件依赖低层组件,低层组件又依赖边侧组件
边侧组件又依赖低层组件,整个系统耦合严重

好莱坞原则允许低层组件将自己挂钩在系统上,由高层组件决定怎么使用和什么时候使用这些低层组件
如在模板方法中的CoffeeDrink中,执行coffee.prepare()Drink是高层组件,Coffee是低层组件,执行coffee.prepare()时,是Drink调用了Coffee中的方法
而不是Drink依赖于Coffee的实现,通过这样来减少依赖

2.8 单一职责原则

每个类只负责自己的事,一个类只负责一个工作

遵循单一职责原则,可以提高类的可读性,提高系统的可维护性
当修改一个功能时,能显著降低对其它功能的影响,分模块按功能的设计类
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值