设计模式
策略者模式
在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。
意图:定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。
主要解决:在有多种算法相似的情况下,使用 if…else 所带来的复杂和难以维护。
何时使用:一个系统有许多许多类,而区分它们的只是他们直接的行为。
如何解决:将这些算法封装成一个一个的类,任意地替换。
关键代码:实现同一个接口。
应用实例: 1、诸葛亮的锦囊妙计,每一个锦囊就是一个策略。 2、旅行的出游方式,选择骑自行车、坐汽车,每一种旅行方式都是一个策略。 3、JAVA AWT 中的 LayoutManager。
优点: 1、算法可以自由切换。 2、避免使用多重条件判断。 3、扩展性良好。
缺点: 1、策略类会增多。 2、所有策略类都需要对外暴露。
使用场景: 1、如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。 2、一个系统需要动态地在几种算法中选择一种。 3、如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。
注意事项:如果一个系统的策略多于四个,就需要考虑使用混合模式,解决策略类膨胀的问题。
开闭原则
对修改关闭、对扩展开放、操作者不能修改我们的代码、但是可以自己去扩展添加自己想要的功能
案例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
public interface Operation {
public int doOperation(int num1, int num2);
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
public class Calculator implements Operation{
private Operation operation;
public void setOperation(Operation operation) { this.operation = operation; }
@Override public int doOperation(int num1, int num2) { return this.operation.doOperation(num1, num2); }
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
public class OperationAdd implements Operation{
@Override public int doOperation(int num1, int num2) { return num1 + num2; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
public class OperationSub implements Operation {
@Override public int doOperation(int num1, int num2) { return num1 - num2; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
public class Test {
public static void main(String[] args) {
Calculator calculator = new Calculator();
calculator.setOperation(new OperationAdd());
System.out.println("加法运算结果为 " + calculator.doOperation(2, 1));
calculator.setOperation(new OperationSub());
System.out.println("减法运算结果为 " + calculator.doOperation(2, 1)); } }
|
装饰模式
装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。
这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。
意图:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。
主要解决:一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。
何时使用:在不想增加很多子类的情况下扩展类。
如何解决:将具体功能职责划分,同时继承装饰者模式。
关键代码: 1、Component 类充当抽象角色,不应该具体实现。 2、修饰类引用和继承 Component 类,具体扩展类重写父类方法。
应用实例: 1、孙悟空有 72 变,当他变成”庙宇”后,他的根本还是一只猴子,但是他又有了庙宇的功能。 2、不论一幅画有没有画框都可以挂在墙上,但是通常都是有画框的,并且实际上是画框被挂在墙上。在挂在墙上之前,画可以被蒙上玻璃,装到框子里;这时画、玻璃和画框形成了一个物体。
优点:装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
缺点:多层装饰比较复杂。
使用场景: 1、扩展一个类的功能。 2、动态增加功能,动态撤销。
注意事项:可代替继承。
案例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
public interface Person {
public BigDecimal cost();
public void show(); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
|
public class XiaoBo implements Person {
@Override public BigDecimal cost() {
return new BigDecimal(0.0); }
@Override public void show() { System.out.println("啥也没有的小博"); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
public abstract class ClothesDecorator implements Person{
protected Person person;
public ClothesDecorator(Person person) { this.person = person; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
|
public class Smoke extends ClothesDecorator{
public Smoke(Person person) { super(person); }
@Override public BigDecimal cost() {
return this.person.cost().add(BigDecimal.valueOf(11.0)); }
@Override public void show() { this.person.show(); System.out.println("买了一包烟、累积消费" + this.cost() + "元"); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
|
public class Lighter extends ClothesDecorator {
public Lighter(Person person) { super(person); }
@Override public BigDecimal cost() { return this.person.cost().add(BigDecimal.valueOf(3.0)); }
@Override public void show() { this.person.show(); System.out.println("买了个火机、累积消费" + this.cost() + "元"); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
public class Test {
public static void main(String[] args) {
Person xiaobo = new XiaoBo();
xiaobo = new Smoke(xiaobo);
xiaobo = new Lighter(xiaobo);
xiaobo.show(); System.out.println("小博总消费 " + xiaobo.cost() + "元"); } }
|
观察者模式
当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知依赖它的对象。观察者模式属于行为型模式。
意图:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
主要解决:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。
何时使用:一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。
如何解决:使用面向对象技术,可以将这种依赖关系弱化。
关键代码:在抽象类里有一个 ArrayList 存放观察者们。
应用实例: 1、拍卖的时候,拍卖师观察最高标价,然后通知给其他竞价者竞价。 2、西游记里面悟空请求菩萨降服红孩儿,菩萨洒了一地水招来一个老乌龟,这个乌龟就是观察者,他观察菩萨洒水这个动作。
优点: 1、观察者和被观察者是抽象耦合的。 2、建立一套触发机制。
缺点: 1、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。 2、如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。 3、观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
使用场景:
一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
一个对象必须通知其他对象,而并不知道这些对象是谁。
需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。
注意事项: 1、JAVA 中已经有了对观察者模式的支持类。 2、避免循环引用。 3、如果顺序执行,某一观察者错误会导致系统卡壳,一般采用异步方式。
案例
1 2 3 4 5 6 7 8 9 10 11 12
|
public abstract class Customer {
public abstract void update(); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
public class CustomerA extends Customer {
@Override public void update() { System.out.println("客户A的报纸已送达"); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
public class CustomerB extends Customer{
@Override public void update() { System.out.println("客户B的报纸已送达"); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
|
public class NewSpaperOffice {
List<Customer> customers = new ArrayList<>();
public void addCustomer(Customer customer){ customers.add(customer); }
public void notifyAllObserver(){ for (Customer customer : customers){ customer.update(); } }
public void newSpaper(){ this.notifyAllObserver(); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
|
public class Test {
public static void main(String[] args) {
NewSpaperOffice newSpaperOffice = new NewSpaperOffice();
newSpaperOffice.addCustomer(new CustomerA()); newSpaperOffice.addCustomer(new CustomerB());
newSpaperOffice.newSpaper(); } }
|
单例模式
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
注意:
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
主要解决:一个全局使用的类频繁地创建与销毁。
何时使用:当您想控制实例数目,节省系统资源的时候。
如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
关键代码:构造函数是私有的。
应用实例:
1、一个班级只有一个班主任。
2、Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。
3、一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。
优点:
1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
2、避免对资源的多重占用(比如写文件操作)。
缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
使用场景:
1、要求生产唯一序列号。
2、WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。
注意事项:getInstance() 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成 instance 被多次实例化。
案例
单线程下的单例模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
public class Single {
private static Single instance;
private Single(){ System.out.println("创建了Single对象"); }
public static Single getInstance(){
if(null == instance){ instance = new Single(); } return instance; } }
|
1 2 3 4 5 6 7 8 9 10 11 12
|
public class Test {
public static void main(String[] args) { for (int i = 0; i < 10; i++) { Single.getInstance(); } } }
|
多线程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
public class Single {
private volatile static Single instance;
private Single(){ System.out.println("创建了Single对象"); }
public static synchronized Single getInstance(){
if(null == instance){ synchronized (Single.class){ if(null == instance){ instance = new Single(); } } } return instance; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
public class Test {
public static void main(String[] args) { for (int i = 0; i < 100; i++) { new Thread(()->{ Single.getInstance(); }).start(); } } }
|
正确的开始、微小的长进、然后持续、嘿、我是小博、带你一起看我目之所及的世界……