java设计模式--结构型模式
-
前言
结构型模式主要是描述如何将类和对象按某种布局组成更大的结构。可以分为类结构型模式和对象结构型模式。
- 类结构型模式:采用继承机制来组织接口和类
- 对象结构型模式:采用组合或聚合来组合对象
结构型模式:
- 适配器模式:将一个类的接口转化成客户希望的另外一个接口,使原本接口不兼容而不能在一起工作的类可以正常运行
- 桥接模式:将抽象与实现分离,使得两部分均可以独立变化。根据合成复用原则,以组合代替继承关系,降低抽象和实现两个可变纬度的耦合度。根据业务场景,这个也可以拆分多个维度
- 装饰模式:动态的给对象增加一些职责,增加其额外功能
- 组合模式:将对象组合程梳妆层次结构,使用户对单个对象和组合对象具有一致的访问型
- 外观模式:为多个复杂的子系统提供一个直直的接口,使得子系统更容易被访问
- 享元模式:通过共享拘束有效支持大量的细粒度对象复用,例如线程池,数据库连接池。
- 代理模式:为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接访问该对象,从而限制增强或修改该对象的一些特性
正文
6. 适配器模式(Adapter)
将某个类的接口转化为客户端期望的另外一个接口。解决兼容性问题,让原本因接口不匹配不能一起工作的两个类可以协同工作。该模式主要分为类结构模型和对象结构型模式,类结构模式耦合性会比较高。如之前所说。组合优于继承。
优点:
- 客户端可以透明的调用目标接口
- 复用现存的类,不需要修改原有代码就可以重用现存的适配者类
- 将目标类和适配者类结构,解决目标类与适配者类接口不一致
- 符合开闭原则
缺点:
- 编写过程需要结合业务场景综合考虑,可能会增加系统的复杂性
- 代码阅读难度增加,可读性变差,尤其是过多使用适配器最终会导致系统代码变得凌乱
应用场景
- 现存的系统满足新系统功能需求的类,但是接口与新系统接口不一致
- 使用第三方提供的组建,但组件接口定义与自己要求的接口定义不同
类适配器UML:
适配者代码如下,返回的是Integer
public class Adaptee {
public Integer specificRequest(){
return 1;
}
}
适配器接口以及适配器
public interface Target {
String request();
}
public class ClassAdapter extends Adaptee implements Target {
@Override
public String request() {
Integer value = specificRequest();
return String.valueOf(value);
}
}
测试
public class Client {
public static void main(String[] args) {
Target target = new ClassAdapter();
String request = target.request();
System.out.println(request); // 1
}
}
小结:
适配器模式使用场景应当是现存接口仍在正常使用,或者业务逻辑复杂暂时无法明确,可以通过适配器进行适配接口。
7. 桥接模式(Bridge)
桥接模式就是将实现与抽象放在两个不同类层次中,基于最小设计原则,用过封装,聚合及继承等行为让不同的类承担不同职责。总之,就是将抽象与行为实现分离,保证各部分独立性以及功能扩展
优点:
- 抽象与实现分离,扩展能力强
- 符合开闭原则
- 符合合成复用原则
- 实现细节对客户端透明
缺点:
- 聚合关系建立在抽象层,针对抽象化设计编程,需要正确识别出系统中两个独立变化文纬度,增加了系统理解与设计难度
应用场景:
- 一个类存在两个独立变化的纬度,并且两纬度都需要进行扩展
- 系统不希望使用继承或者多层次继承导致系统类个数急剧增加
- 一个系统需要在构建的抽象化角色和具体化角色之间增加更多的灵活性
角色
- 抽象化角色(Abstraction):定义抽象类,并包含一个对实现化对象的引用
- 扩展抽象化角色(Refined Abstraction):抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现角色中的业务方法
- 实现化角色(Implementor):定义实现化角色的接口,供扩展抽象化角色调用
- 具体实现化角色(Concrete Implementor):给出实现化角色接口的具体实现
代码实现
实现化
public interface Implementor { void operationImpl(); } public class ConcreteImplementorA implements Implementor { @Override public void operationImpl() { System.out.println("ConcreteImplementorA"); } } public class ConcreteImplementorB implements Implementor { @Override public void operationImpl() { System.out.println("ConcreteImplementorB"); } }
抽象化
public abstract class Abstraction { protected Implementor implementor; protected Abstraction(Implementor implementor){ this.implementor = implementor; } public abstract void operation(); } // 根据业务场景,如果有多个,可以构造多个扩展抽象化角色 public class RefinedAbstraction extends Abstraction { protected RefinedAbstraction(Implementor implementor) { super(implementor); } @Override public void operation() { System.out.println("RefinedAbstraction, if there needs, the could be more RefinedAbstraction"); implementor.operationImpl(); } }
调用
public class Client { public static void main(String[] args) { Implementor implementorA = new ConcreteImplementorA(); Implementor implementorB = new ConcreteImplementorB(); RefinedAbstraction abstractionA = new RefinedAbstraction(implementorA); RefinedAbstraction abstractionB = new RefinedAbstraction(implementorB); abstractionA.operation(); System.out.println("---------"); abstractionB.operation(); /** * RefinedAbstraction, if there needs, the could be more RefinedAbstraction * ConcreteImplementorA * --------- * RefinedAbstraction, if there needs, the could be more RefinedAbstraction * ConcreteImplementorB */ } }
小结:
桥接模式常见的使用场景就是替换多层继承,可以减少子类的个数,降低系统的管理和维护成本,继承确实有较多优点,继承可以很好实现代码复用的功能,但是这个封装特性也是继承的缺点,因为子类会继承父类所有的方法,根据合成复用原则,优先使用组合或者聚合。
8. 装饰模式(Decorator)
在不改变现有对象结构情况下,动态给对象增加职责。举个例子,对于登陆展示功能,要求增加一个一句欢迎的话语,并且要求不能修改原有接口,这个时候就可以使用装饰模式
优点:
- 对继承的不成,相对于继承灵活,在不改变原有对象前提下,动态给对象扩展功能,遵从开闭原则
- 通过装饰器的排列组合实现不同功能
缺点:
- 会增加子类个数
- 适用场景:
- 需要给某个现有类添加额外功能,且不能修改原有类。例如,对final修改的类进行功能扩充
- 对想有一组基本功能进行排列组合会产生较多的功能,采用继承较难实现
- 对象的功能要求可以动态添加
装饰模式UML
代码如下:
首先定义接口以及具体构件(构件也可以投多种实现)
public interface Component { void operation(); } public class ConcreteComponent implements Component { @Override public void operation() { System.out.println("i am concrete component"); } }
装饰:
public abstract class Decorator implements Component { protected Component component; public Decorator(Component component){ this.component = component; } public abstract void operation(); } public class ConcreteDecorateA extends Decorator { public ConcreteDecorateA(Component component) { super(component); } @Override public void operation() { component.operation(); this.addFunction(); } private void addFunction() { System.out.println("add function in concrete decorate A"); } } public class ConcreteDecorateB extends Decorator { public ConcreteDecorateB(Component component) { super(component); } @Override public void operation() { this.addFunction(); component.operation(); } private void addFunction() { System.out.println("this add function in concrete decorator B"); } }
实现:
public class Client { public static void main(String[] args) { Component component = new ConcreteComponent(); Component decorateA = new ConcreteDecorateA(component); Component decorateB = new ConcreteDecorateB(decorateA); decorateB.operation(); /** * this add function in concrete decorator B * i am concrete component * add function in concrete decorate A */ } }
9. 组合模式(Composite Pattern)
又称整体-部分(Part-Whole)模式,将对象组合成树状的层次接口的模式。用以表示“整体-部分”关系,是客户端对单个对象以及组合队形具有一致的访问性
优点:
- 客户端可以一致性的处理单个对象与组合对象,简化客户端代码
- 组合体内加入新对象,不用修改客户端源码,符合开闭原则
缺点:
- 设计较复杂,客户端需要时间理清类之间的层次关系
- 不容易限制容器中的构件
- 不易用继承方法增加构件新功能
- 树叶构件许阿哟实现部分无用的方法
适用场景:
- 需要表示一个对象整体与部分的层次结构,方便创建复杂的层次结构,客户端午休会内部的组成细节
- 需要保证对象与组合的一致性接口
- 要求遍历组织结构或者处理的对象具有谁能够结构是
- 要求有较高的抽象性,树叶构件以及树枝构件不能有太大的差异性
组合模式UML:
代码如下:
构件接口
public interface Component { void add(Component component); void remove(Component component); Component getChild(int t); void operation(); }
树枝构件
public class Composite implements Component { private String name; private List<Component> children = new ArrayList<>(); public Composite(String name){ this.name = name; } @Override public void add(Component component) { children.add(component); } @Override public void remove(Component component) { children.remove(component); } @Override public Component getChild(int t) { if (children.size() > t) return children.get(t); return null; } @Override public void operation() { System.out.println("this is composite, name : " + this.name); for (Component child : children) { child.operation(); } } }
树叶构件
public class Leaf implements Component { String name; public Leaf(String name) { this.name = name; } @Override public void add(Component component) { throw new RuntimeException(); } @Override public void remove(Component component) { throw new RuntimeException(); } @Override public Component getChild(int t) { throw new RuntimeException(); } @Override public void operation() { System.out.println("this is leaf, name: " + this.name); } }
客户端
public class Client { public static void main(String[] args) { Leaf leafA = new Leaf("leafA"); Leaf leafB = new Leaf("leafB"); Composite compositeA = new Composite("compositeA"); Composite compositeB = new Composite("compositeB"); compositeA.add(leafA); compositeB.add(leafB); Composite compositeC = new Composite("compositeC"); compositeC.add(compositeA); compositeC.add(compositeB); compositeC.operation(); /** * this is composite, name : compositeC * this is composite, name : compositeA * this is leaf, name: leafA * this is composite, name : compositeB * this is leaf, name: leafB */ } }
小结:
组合模式组号保证树叶构件与树枝构件有较好的抽象性,否则会增加较多冗余方法。
10. 外观模式(Facade)
通过为多个复杂的子系统提供一致性访问接口,从而使子系统更容易被访问。举个例子,比如在进行使用阿里云oss时,我们会进行封装将配置信息以及调用分别进行封装,最终暴露给业务端使用的就是一个简单的接口,业务端很容易调用这个接口进行存储。
优点:
- 降低子系统与客户端之间的耦合度,使得子系统的变化不会影响客户端
- 对客户端屏蔽子系统组件,减少客户端处理项目,使得客户端可以容易使用子系统
- 减低依赖性
缺点:
- 不宜限制客户端使用的子系统类
- 增加新的子系统,需要修改外观类或者客户端,违背开闭原则
适用场景:
- 复杂系统的子系统很多,需要提供一个统一的对外提供接口
- 客户端多个子系统之间存在较大联系,引入外观模式可将其分离
外观模式UML:
代码:
public class SubSystem1 { public void method1(){ System.out.println("SubSystem1"); } } public class SubSystem2 { public void method2(){ System.out.println("SubSystem2"); } } public class SubSystem3 { public void method3(){ System.out.println("SubSystem3"); } } public class Facade { private SubSystem1 subSystem1 = new SubSystem1(); private SubSystem2 subSystem2 = new SubSystem2(); private SubSystem3 subSystem3 = new SubSystem3(); public void method() { subSystem1.method1(); subSystem2.method2(); subSystem3.method3(); } } public class Client { public static void main(String[] args) { Facade facade = new Facade(); facade.method(); /** * SubSystem1 * SubSystem2 * SubSystem3 */ } }
小结:
外观模式通过定义一个一致接口,用以屏蔽内部子系统的细节,是的调用端只需要跟这个接口发生调用,无需关心内部细节。使用外观模式可以帮助客户端与子系统进行解耦,是的子系统内部模块更以维护以及扩展。合理使用外观模式,可以帮助我们更好的划分访问的层次。如果子系统很简单,就不要过多使用外观模式,增加温乎成本。所有的思想都是系统有层次,利于维护。
11. 享元模式(Flyweight Pattern)
又称蝇量模式,就是运用共享技术邮箱的支持大量细粒度的对象。也就是共享一寸的对象来减少需要创建的对象数量,避免大量相似的开销,从而提升资源的利用率
优点:
- 解决重复对象的内存浪费的问题
缺点:
- 因为对象需要共享,因此需要将一些无妨共享的状态,在外部进行处理
适用场景:
- 系统中大量相同或者相似的对象,这些对象耗费大量的内存资源
- 大部分对象可以按照内部状态进行分组
- 最经典的应用就是池技术,例如JVM的常量池,数据库连接池,线程池
享元模式的UML:
各角色定义:
- 抽象享元角色:所有的享元类的基类,提供享元角色过饭。非享元的外部状态以参数形式通过方法注入
- 具体享元角色:实现类
- 非享元角色:不可以共享的外部状态,以参数的形式注入具体享元方法中
- 享元工厂:负责创建与管理享元角色。当客户端请求享元对象,享元工厂检查系统中是否存在符合要求的享元对象,如存在则提供给客户端,否则进行创建
代码如下:
抽象享元角色:
public interface Flyweight { void operation(UnsharedConcreteFlyweight state); }
具体享元角色:
public class ConcreteFlyweight1 implements Flyweight { public String key; public ConcreteFlyweight1(String key) { System.out.println("create ConcreteFlyweight1: " + key); this.key = key; } @Override public void operation(UnsharedConcreteFlyweight state) { System.out.println("ConcreteFlyweight1 has been used: " + key); System.out.println("unShared is: " + (Objects.nonNull(state) ? state.getInfo() : "nothing")); } } public class ConcreteFlyweight2 implements Flyweight { public String key; public ConcreteFlyweight2(String key) { System.out.println("create ConcreteFlyweight2: " + key); this.key = key; } @Override public void operation(UnsharedConcreteFlyweight state) { System.out.println("ConcreteFlyweight2 has been used: " + key); System.out.println("unShared is: " + (Objects.nonNull(state) ? state.getInfo() : "nothing")); } }
非共享享元角色:
public class UnsharedConcreteFlyweight { private String info; public UnsharedConcreteFlyweight(String info) { this.info = info; } public String getInfo() { return info; } public void setInfo(String info) { this.info = info; } }
享元工厂角色:
public class FlyweightFactory { private final Map<String, Flyweight> flyweights = new HashMap<>(); private int count = 0; public Flyweight getFlyweight(String key) { if (!flyweights.containsKey(key)) { synchronized (this) { if (!flyweights.containsKey(key)) { // 这里取决于业务场景 flyweights.put(key, count % 2 == 1 ? new ConcreteFlyweight1(key) : new ConcreteFlyweight2(key)); count++; } } } return flyweights.get(key); } }
客户端:
public class Client { public static void main(String[] args) { FlyweightFactory factory = new FlyweightFactory(); Flyweight a01 = factory.getFlyweight("a"); Flyweight a02 = factory.getFlyweight("a"); Flyweight b01 = factory.getFlyweight("b"); Flyweight b02 = factory.getFlyweight("b"); Flyweight c01 = factory.getFlyweight("c"); Flyweight c02 = factory.getFlyweight("c"); a01.operation(new UnsharedConcreteFlyweight("first")); a02.operation(new UnsharedConcreteFlyweight("second")); b01.operation(new UnsharedConcreteFlyweight("first")); b02.operation(new UnsharedConcreteFlyweight("second")); c01.operation(new UnsharedConcreteFlyweight("first")); c02.operation(new UnsharedConcreteFlyweight("second")); /** * create ConcreteFlyweight2: a * create ConcreteFlyweight1: b * create ConcreteFlyweight2: c * ConcreteFlyweight2 has been used: a * unShared is: first * ConcreteFlyweight2 has been used: a * unShared is: second * ConcreteFlyweight1 has been used: b * unShared is: first * ConcreteFlyweight1 has been used: b * unShared is: second * ConcreteFlyweight2 has been used: c * unShared is: first * ConcreteFlyweight2 has been used: c * unShared is: second */ } }
小结:
享元工厂也可以认为是工厂模式的扩展类,将相当于在中间加了一层缓冲。使用享元模式,最好是系统中存在大量的相似对象或频繁使用对象。否则没有必要。
享元模式提高了系统的复杂度,需要分离内部状态以及外部状态,外部状态具有固化属性,不应岁内部章台变化而变化
12. 代理模式(Proxy)
为一个对象提供一个替身,以便控制这个对象的访问。通过代理对象访问目标对象。
优点:
- 可以在客户端与目标对象之间起到一个中介作用以及保护目标对象的作用
- 扩展目标对象的功能
- 将客户端与目标对象分离,一定程度上降低了耦合性,增加了程序的可扩展性
缺点:
- 造成系统中类的增加
- 请求速度变慢,因为中间加了一层代理对象
- 增加系统的复杂度
应用场景:
- 远程代理
- 防火墙代理
- 缓存代理
- 安全代理
- 同步代理
代理模式的UML:
代码如下,代理模式比较简单:
public interface Subject { void request(); } public class RealSubject implements Subject { @Override public void request() { System.out.println("real request"); } } public class Proxy implements Subject { private final RealSubject realSubject; public Proxy(RealSubject realSubject) { this.realSubject = realSubject; } @Override public void request() { this.preRequest(); this.realSubject.request(); this.postRequest(); } private void preRequest() { System.out.println("pre request"); } private void postRequest() { System.out.println("post request"); } } public class Client { public static void main(String[] args) { Proxy proxy = new Proxy(new RealSubject()); proxy.request(); /** * pre request * real request * post request */ } }
上述属于静态代理,静态代理有一个缺点:
代理对象需要跟目标对象实现一样的接口,并且一旦接口增加方法,目标对象与代理对象都要维护
学过java都知道还有一个动态代理(接口代理)以及cglib代理,这个代理对象不需要实现接口,维护以及扩展都比较方便
看一下动态代理的反射以及客户端调用
public class ProxyFactory { private Object target; public ProxyFactory(Object target) { this.target = target; } public Object getInstant(){ return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), (proxy, method, args) -> { System.out.println("pre request"); Object invoke = method.invoke(target, args); System.out.println("post request"); return invoke; }); } } public class Client { public static void main(String[] args) { DynamicSubject dynamicRealSubject = new DynamicRealSubject(); ProxyFactory proxyFactory = new ProxyFactory(dynamicRealSubject); DynamicSubject dynamicSubject = (DynamicSubject) proxyFactory.getInstant(); dynamicSubject.request(); /** * pre request * real request * post request */ } }
使用动态代理必须实现接口,对于没有接口的需要使用cglib进行代理
cglib代理也叫做子类代理,他是在内存中构建一个子类对象从事实现对目标对象的动态代理,注意类不能是final,否则会报错,因为无法继承,目标方法不能是final或者static,否则不会执行目标独享额外的方法
被代理对象
public class CglibObject { public void request() { System.out.println("i am real object"); } }
代理类
public class ProxyFactory implements MethodInterceptor { private final Object target; public ProxyFactory(Object target) { this.target = target; } public Object getInstance() { // 创建一个工具类 Enhancer enhancer = new Enhancer(); // 设置父类 enhancer.setSuperclass(target.getClass()); // 设置回调函数 enhancer.setCallback(this); // 返回子类对象,即代理对象 return enhancer.create(); } @Override public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { System.out.println("pre request"); Object invoke = method.invoke(target, args); System.out.println("post request"); return invoke; } }
客户端
public class Client { public static void main(String[] args) { CglibObject cglibObject = new CglibObject(); ProxyFactory proxyFactory = new ProxyFactory(cglibObject); CglibObject instance = (CglibObject) proxyFactory.getInstance(); instance.request(); /** * pre request * i am real object * post request */ } }
小结:
通过代理模式访问目标对象,这样足以哦可以再目标对象实现的基础上,额外增强功能,并且实现一定程度的接口,但是也会造成额外的开销。