导读:设计模式能够帮助我们优化代码结构,让代码更优雅灵活。有哪些常见的设计模式?如何合理运用?本文分享作者对工厂模式、单例模式、装饰模式、策略模式、代理模式和观察者模式的理解,介绍每种模式的模式结构、优缺点、适用场景、注意实现及代码实现。
小明追妹子的时候,请她喝了不少咖啡,她爱喝卡布奇诺,每次去咖啡店,只要跟服务员说“来杯卡布奇诺”就行了,虽然各家的口味有些不同,但是不管是星爸爸还是Costa,都能够提供卡布奇诺这种咖啡。这里的星爸爸和Costa就是生产咖啡的工厂。
-
Factory:工厂角色-负责实现创建所有实例的内部逻辑.
-
Product:抽象产品角色-是所创建的所有对象的父类,负责描述所有实例所共有的公共接口。
-
ConcreteProduct:具体产品角色-是创建目标,所有创建的对象都充当这个角色的某个具体类的实例。
-
优点:客户类和工厂类分开。消费者任何时候需要某种产品,只需向工厂请求即可。消费者无须修改就可以接纳新产品。
-
缺点:是当产品修改时,工厂类也要做相应的修改。
以前经常带老婆去优衣库(简单工厂)买衣服,就那么多款式,逛的次数多了,她就烦了。后来我改变策略,带老婆去逛商场(抽象工厂),商场里有各式品牌的店铺,不用我管,她自己就能逛上一整天。 区别于简单工厂,核心工厂类(商场)不再负责所有产品的创建,而是将具体创建的工作交给子类(服装店)去做,成为一个抽象工厂角色,仅负责给出具体工厂类必须实现的接口(门店),而不接触哪一个产品类应当被实例化这种细节。
-
Product:抽象产品
-
ConcreteProduct:具体产品
-
Factory:抽象工厂
-
ConcreteFactory:具体工厂
package FactoryMethod;
public class FactoryPattern
{
public static void main(String[] args)
{
Factory factory = new ConcreteFactoryA();
Product product = factory.createProduct();
product.use();
}
}
//抽象产品:提供了产品的接口
interface Product
{
public void use();
}
//具体产品A:实现抽象产品中的抽象方法
class ConcreteProductA implements Product
{
public void use()
{
System.out.println("具体产品A显示...");
}
}
//具体产品B:实现抽象产品中的抽象方法
class ConcreteProductB implements Product
{
public void use()
{
System.out.println("具体产品B显示...");
}
}
//抽象工厂:提供了厂品的生成方法
interface Factory
{
public Product createProduct();
}
//具体工厂A:实现了厂品的生成方法
class ConcreteFactoryA implements AbstractFactory
{
public Product createProduct()
{
System.out.println("具体工厂A生成-->具体产品A.");
return new ConcreteProductA();
}
}
//具体工厂B:实现了厂品的生成方法
class ConcreteFactoryB implements AbstractFactory
{
public Product createProduct()
{
System.out.println("具体工厂B生成-->具体产品B.");
return new ConcreteProductB();
}
}
韦小宝有7个老婆,但是每个都只有他这一个老公,他的所有老婆叫老公时,指的都是他,他就是一个单例。
-
Singleton:单例
-
优点:全局只有一个实例,便于统一控制,同时减少了系统资源开销。
-
缺点:没有抽象层,扩展困难。
public class Singleton
{
private static volatile Singleton instance=null; //保证 instance 在所有线程中同步
private Singleton(){} //private 避免类在外部被实例化
public static synchronized Singleton getInstance()
{
//getInstance 方法前加同步
if(instance == null)
{
instance = new Singleton();
}
return instance;
}
}
大学毕业,想要送给室友一个有纪念意义的礼物,就找到一张大家的合照,在上面写上“永远的兄弟!”,然后拿去礼品店装了个相框,再包上礼盒。这里的我和礼品店都是装饰器,都没有改变照片本身,却都让照片变得更适合作为礼物送人。
-
Component:抽象构件
-
ConcreteComponent:具体构件
-
Decorator:抽象装饰类
-
ConcreteDecorator:具体装饰类
-
优点:比继承更加灵活(继承是耦合度很大的静态关系),可以动态的为对象增加职责,可以通过使用不同的装饰器组合为对象扩展N个新功能,而不会影响到对象本身。
-
缺点:当一个对象的装饰器过多时,会产生很多的装饰类小对象和装饰组合策略,增加系统复杂度,增加代码的阅读理解成本。
-
适合需要(通过配置,如:diamond)来动态增减对象功能的场景。
-
适合一个对象需要N种功能排列组合的场景(如果用继承,会使子类数量爆炸式增长)
-
一个装饰类的接口必须与被装饰类的接口保持相同,对于客户端来说无论是装饰之前的对象还是装饰之后的对象都可以一致对待。
-
尽量保持具体构件类Component作为一个“轻”类,也就是说不要把太多的逻辑和状态放在具体构件类中,可以通过装饰类。
package decorator;
public class DecoratorPattern
{
public static void main(String[] args)
{
Component component = new ConcreteComponent();
component.operation();
System.out.println("---------------------------------");
Component decorator = new ConcreteDecorator(component);
decorator.operation();
}
}
//抽象构件角色
interface Component
{
public void operation();
}
//具体构件角色
class ConcreteComponent implements Component
{
public ConcreteComponent()
{
System.out.println("创建具体构件角色");
}
public void operation()
{
System.out.println("调用具体构件角色的方法operation()");
}
}
//抽象装饰角色
class Decorator implements Component
{
private Component component;
public Decorator(Component component)
{
this.component=component;
}
public void operation()
{
component.operation();
}
}
//具体装饰角色
class ConcreteDecorator extends Decorator
{
public ConcreteDecorator(Component component)
{
super(component);
}
public void operation()
{
super.operation();
addBehavior();
}
public void addBehavior()
{
System.out.println("为具体构件角色增加额外的功能addBehavior()");
}
}
男生追妹子时,一般都会用到这种模式,常见的策略有这些:约会吃饭;看电影;看演唱会;逛街;去旅行……,虽然做的事情不同,但可以相互替换,唯一的目标都是捕获妹子的芳心。
-
Context: 环境类
-
Strategy: 抽象策略类
-
ConcreteStrategy: 具体策略类
-
优点:策略模式提供了对“开闭原则”的完美支持,用户可以在不修改原有系统的基础上选择算法或行为。干掉复杂难看的if-else。
-
缺点:调用时,必须提前知道都有哪些策略模式类,才能自行决定当前场景该使用何种策略。
package strategy;
public class StrategyPattern
{
public static void main(String[] args)
{
Context context = new Context();
Strategy strategyA = new ConcreteStrategyA();
context.setStrategy(strategyA);
context.algorithm();
System.out.println("-----------------");
Strategy strategyB = new ConcreteStrategyB();
context.setStrategy(strategyB);
context.algorithm();
}
}
//抽象策略类
interface Strategy
{
public void algorithm(); //策略方法
}
//具体策略类A
class ConcreteStrategyA implements Strategy
{
public void algorithm()
{
System.out.println("具体策略A的策略方法被访问!");
}
}
//具体策略类B
class ConcreteStrategyB implements Strategy
{
public void algorithm()
{
System.out.println("具体策略B的策略方法被访问!");
}
}
//环境类
class Context
{
private Strategy strategy;
public Strategy getStrategy()
{
return strategy;
}
public void setStrategy(Strategy strategy)
{
this.strategy=strategy;
}
public void algorithm()
{
strategy.algorithm();
}
}
淘宝店客服总是会收到非常多的重复问题,例如:有没有现货?什么时候发货?发什么快递?大量回答重复性的问题太烦了,于是就出现了小蜜机器人,他来帮客服回答那些已知的问题,当碰到小蜜无法解答的问题时,才会转到人工客服。这里的小蜜机器人就是客服的代理。
-
Subject: 抽象主题角色
-
Proxy: 代理主题角色
-
RealSubject: 真实主题角色
-
优点:代理可以协调调用方与被调用方,降低了系统的耦合度。根据代理类型和场景的不同,可以起到控制安全性、减小系统开销等作用。
-
缺点:增加了一层代理处理,增加了系统的复杂度,同时可能会降低系统的相应速度。
-
远程(Remote)代理:为一个位于不同的地址空间的对象提供一个本地的代理对象,这个不同的地址空间可以是在同一台主机中,也可是在另一台主机中,远程代理又叫做大使(Ambassador)。
-
虚拟(Virtual)代理:如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。
-
Copy-on-Write代理:它是虚拟代理的一种,把复制(克隆)操作延迟到只有在客户端真正需要时才执行。一般来说,对象的深克隆是一个开销较大的操作,Copy-on-Write代理可以让这个操作延迟,只有对象被用到的时候才被克隆。
-
保护(Protect or Access)代理:控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。
-
缓冲(Cache)代理:为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。
-
防火墙(Firewall)代理:保护目标不让恶意用户接近。
-
同步化(Synchronization)代理:使几个用户能够同时使用一个对象而没有冲突。
-
智能引用(Smart Reference)代理:当一个对象被引用时,提供一些额外的操作,如将此对象被调用的次数记录下来等。
package proxy;
public class ProxyPattern
{
public static void main(String[] args)
{
Proxy proxy = new Proxy();
proxy.request();
}
}
//抽象主题
interface Subject
{
void request();
}
//真实主题
class RealSubject implements Subject
{
public void request()
{
System.out.println("访问真实主题方法...");
}
}
//代理
class Proxy implements Subject
{
private RealSubject realSubject;
public void request()
{
if (realSubject==null)
{
realSubject=new RealSubject();
}
preRequest();
realSubject.request();
afterRequest();
}
public void preRequest()
{
System.out.println("访问真实主题之前的预处理。");
}
public void afterRequest()
{
System.out.println("访问真实主题之后的后续处理。");
}
}
出差在外,想了解孩子在家的情况,这时候只要加入“相亲相爱一家人”群,老爸老妈会经常把孩子的照片和视频发到群里,你要做的就是作为一个观察者,刷一刷群里的信息就能够了解一切了。
-
Subject:目标
-
ConcreteSubject:具体目标
-
Observer:观察者
-
ConcreteObserver:具体观察者
-
优点:将复杂的串行处理逻辑变为单元化的独立处理逻辑,被观察者只是按照自己的逻辑发出消息,不用关心谁来消费消息,每个观察者只处理自己关心的内容。逻辑相互隔离带来简单清爽的代码结构。
-
缺点:观察者较多时,可能会花费一定的开销来发消息,但这个消息可能仅一个观察者消费。
package observer;
import java.util.*;
public class ObserverPattern
{
public static void main(String[] args)
{
Subject subject = new ConcreteSubject();
Observer obsA = new ConcreteObserverA();
Observer obsb = new ConcreteObserverB();
subject.add(obsA);
subject.add(obsB);
subject.setState(0);
}
}
//抽象目标
abstract class Subject
{
protected List<Observer> observerList = new ArrayList<Observer>();
//增加观察者方法
public void add(Observer observer)
{
observers.add(observer);
}
//删除观察者方法
public void remove(Observer observer)
{
observers.remove(observer);
}
public abstract void notify(); //通知观察者方法
}
//具体目标
class ConcreteSubject extends Subject
{
private Integer state;
public void setState(Integer state){
this.state = state;
// 状态改变通知观察者
notify();
}
public void notify()
{
System.out.println("具体目标状态发生改变...");
System.out.println("--------------");
for(Observer obs:observers)
{
obs.process();
}
}
}
//抽象观察者
interface Observer
{
void process(); //具体的处理
}
//具体观察者A
class ConcreteObserverA implements Observer
{
public void process()
{
System.out.println("具体观察者A处理!");
}
}
//具体观察者B
class ConcreteObserverB implements Observer
{
public void process()
{
System.out.println("具体观察者B处理!");
}
}
作者: 铭劭