结构类模式(读书笔记)
最近在读秦小波写的设计模式之禅这本书,结构类模式读完了,写下一篇文章做下总结。结构类模式包括适配器模式、桥梁模式、组合模式、装饰模式、门面模式、享元模式和代理模式。为什么叫结构类模式呢?因为它们都是通过组合类或对象产生更大结构以适应更高层次的逻辑需求。
适配器模式(Adapter Pattern)
定义:将一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。通用类图如下:
1-1 adpater.png如何理解呢?
从通用类图中可以看到有三个角色,Adaptee源角色、Target目标角色、Adapter适配器角色,Target就是客户端所期待的接口,Adaptee就是要被转换的角色,通常它是已经存在的、运行良好的类或对象,Adapter的作用就是把Adaptee转换成为Target。简单来说就是有A和B两个不同的类,如果想让A类像B类一样使用,这个时候就可以通过一个适配器C类,适配器C类负责把A类转换成B类,这样使用A类就可以像使用B类那样。
适配器模式的使用场景有哪些?只要记住一点就可以了:你有动机修改一个已经投产的接口时,就可以考虑适配器模式了。比如系统扩展了,需要使用一个已有或新建立的类,但这个类又不符合系统的接口,可以使用适配器模式解决这个问题。
适配器模式通用源码
public interface Target {
// 目标角色有自己的方法
public void request();
}
public class ConcreteTarget implements Target {
@Override
public void request() {
System.out.println("If you need any help, please call me.");
}
}
public class Adaptee {
// 原有的业务逻辑
public void doSomething() {
System.out.println("我非常忙");
}
}
public class Adapter extends Adaptee implements Target {
@Override
public void request() {
super.doSomething();
}
}
// 在Client场景类中使用它
public class Client {
public static void main(String[] args) {
// 原有业务逻辑
Target target = new ConcreteTarget();
target.request();
// 增加了适配器后的角色
Target target1 = new Adapter();
target1.request();
}
}
适配模式的扩展
前面所说的可以被称为类适配器,下面介绍一种对象适配器,是适配器模式的一种扩展,通用类图如下:
1-2 adaptee2.png比较两个通用类图,发现一种是Adapter继承于Adaptee,一种是Adapter关联Adaptee1和Adaptee2,所以通过继承关系的叫做类适配器,通过关联关系的叫做对象适配器。类适配器与对象适配器的区别:类适配器是类间继承,对象适配器是对象的合成关系,也可以说是类的关联关系。
注意点:Adapter适配器不要加入自己的实现,它所有的实现都是委托给Adaptee源角色来实现的。
代理模式(Proxy Pattern)
定义:为其他对象提供一种代理以控制这个对象的访问。通用类图如下:
1-3 proxy.png如何理解代理模式呢?
通过一个例子来理解,比如一家公司请明星为自己的产品代言,这家公司如果直接联系该明星,明星就会说这件事请和我的经纪人联系,换句话说就是想要请明星代言,先要找到其经纪人,通过经纪人在去和明星进行商讨。在这里经纪人就可以理解为一个代理,通过经纪人来对明星进行访问,这就是代理模式。
代理模式的使用场景?比如Spring AOP,非常典型的动态代理。
代理模式通用源码
public interface Subject {
public void request();
}
public class RealSubject implements Subject {
@Override
public void request() {
// 业务逻辑处理
}
}
public class Proxy implements Subject {
// 要代理哪个实现类
private Subject subject = null;
public Proxy(Subject subject) {
this.subject = subject;
}
@Override
public void request() {
this.before();
this.subject.request();
this.after();
}
// 预处理
private void before() {
// do something
}
// 善后处理
private void after() {
// do something
}
}
// Client场景类
public class Client {
public static void main(String[] args) {
Subject subject = new RealSubject();
Proxy proxy = new Proxy(subject);
proxy.request();
}
}
代理模式的扩展
扩展一:普通代理
它要求客户端只能访问代理角色,而不能访问真实角色。
扩展二:强制代理
要求客户端必须通过真实角色找到代理角色,否则不能访问真实角色。
扩展三:代理是有个性的
一个类可以实现多个接口,完成不同任务的整合。也就是说代理类不仅仅可以实现主题接口,也可以实现其他接口完成不同的任务,而且代理的目的是在目标对象的基础上作增强,这种增强的本质通常就是对目标对象的方法进行拦截和过滤。
扩展四:动态代理
动态代理通用类图如下:
1-4 dynamic-proxy.png动态代理是在实现阶段不用关心代理谁,而在运行阶段才指定代理哪一个对象。
装饰模式(Decorator Pattern)
定义:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式比生成子类更为灵活。通用类图如下:
1-5 decorator.png如何理解装饰呢?
装饰类比较简单,看下其使用场景:需要扩展一个类的功能,或给一个类增加附加功能;需要动态地给一个对象增加功能,这些功能可以再动态地撤销;需要为一批兄弟类进行改装或加装功能。
装饰模式通用源码
public abstract class Component {
public abstract void operate();
}
public class ConcreteComponent extends Component {
@Override
public void operate() {
System.out.println("do something");
}
}
public class Decorator extends Component {
private Component component = null;
public Decorator(Component component) {
this.component = component;
}
@Override
public void operate() {
this.component.operate();
}
}
public class ConcreteDecorator1 extends Decorator {
public ConcreteDecorator1(Component component) {
super(component);
}
public void method1() {
System.out.println("method1 修饰");
}
@Override
public void operate() {
this.method1();
super.operate();
}
}
public class ConcreteDecorator2 extends Decorator {
public ConcreteDecorator2(Component component) {
super(component);
}
public void method2() {
System.out.println("method2 修饰");
}
@Override
public void operate() {
this.method2();
super.operate();
}
}
public class Client {
public static void main(String[] args) {
Component component = new ConcreteComponent();
// 修饰1
component = new ConcreteDecorator1(component);
// 修饰2
component = new ConcreteDecorator2(component);
component.operate();
}
}
门面模式(Facade Pattern)
定义:要求一个子系统的外部与其内部的通讯必须通过一个统一的对象进行。门面模式提供一个高层次的接口,使得子系统更易于使用。通用类图如下:
1-6 facade.png如何理解门面模式呢?
简单来说,门面对象是外界访问子系统内部的唯一通道。比如有A、B、C三个子系统,要想访问这三个子系统,必须通过一个统一的门面对象来访问。
门面模式的使用场景:为一个复杂的模块或子系统提供一个供外界访问的接口;子系统相对独立(外界对系统的访问只要黑箱操作即可)。
注意点:门面不参与子系统内的业务逻辑。
门面模式通用源码
public class ClassA {
// 子系统A
public void doSomethingA() {
System.out.println("do something A");
}
}
public class ClassB {
// 子系统B
public void doSomethingB() {
System.out.println("do something B");
}
}
public class ClassC {
// 子系统C
public void doSomethingC() {
System.out.println("do something C");
}
}
public class Facade {
// 被委托的对象
ClassA classA = new ClassA();
ClassB classB = new ClassB();
ClassC classC = new ClassC();
// 提供给外部的访问方法
public void methodA() {
this.classA.doSomethingA();
}
public void methodB() {
this.classB.doSomethingB();
}
public void methodC() {
this.classC.doSomethingC();
}
}
组合模式(Composite Pattern)
定义:将对象组合成树形结构以表示部分-整体的层次结构,使得用户对单个对象和组合对象的使用具有一致性。通用类图如下:
1-7 composite.png如何理解组合模式呢?
Component抽象构件角色:定义参加组合对象的共有方法和属性,可以定义一些默认的行为或属性;Leaf叶子构件:叶子对象,其下再也没有其他的分支,也就是遍历的最小单位;Composite树枝构件:树枝对象,它的作用是组合树枝节点和叶子节点形成一个树形结构。
组合模式的使用场景:维护和展示部分-整体关系的场景,如树形菜单、文件和文件夹管理;从一个整体中能够独立出部分模块或功能的场景。
组合模式通用源码
public abstract class Component {
// 个体和整体都具有的共享 抽象构件
public void doSomething() {
// 编写业务逻辑
System.out.println("个体和整体都具有的共享");
}
}
public class Composite extends Component {
// 构件容器
private ArrayList<Component> componentArrayList = new ArrayList<Component>();
// 增加一个叶子构件或树枝构件
public void add(Component component) {
this.componentArrayList.add(component);
}
// 删除一个叶子构件或树枝构件
public void remove(Component component) {
this.componentArrayList.remove(component);
}
// 获得分支下的所有叶子构件和树枝构件
public ArrayList<Component> getChildren() {
return this.componentArrayList;
}
}
public class Leaf extends Component {
// 树叶构件
}
// 场景类
public class Client {
public static void main(String[] args) {
// 创建一个根节点
Composite root = new Composite();
root.doSomething();
// 创建一个树枝构件
Composite branch = new Composite();
// 创建一个叶子节点
Leaf leaf = new Leaf();
// 建立整体
root.add(branch);
branch.add(leaf);
display(root);
}
// 通过递归遍历树
public static void display(Composite root) {
for (Component c : root.getChildren()) {
if (c instanceof Leaf) {
// 叶子结点
c.doSomething();
}else {
// 树枝结点
display((Composite) c);
}
}
}
}
桥梁模式(Bridge Pattern)
定义:将抽象和实现解耦,使得两者可以独立地变化。通用类图如下:
1-8 bridge.png如何理解桥梁模式呢?
简单来理解就是把经常变化的部分封装到Implementor中,不经常变化的留在抽象中。如果抽闲的实现类想要调用Implementor中的方法怎么办?搭个桥过去,也就是在实现类中保留一个Implementor的引用,这也是为什么叫桥梁模式的原因。最终的目的就是把抽象和实现分离开来。
使用场景有哪些呢?不希望或不适用使用继承的场景,例如继承层次过渡、无法更细化设计颗粒等场景,需要考虑使用桥梁模式;接口或抽象类不稳定的场景;重用性要求较高的场景,设计的颗粒度越细,则被重用的可能性就越大;
桥梁模式通用源码
public interface Implementor {
// 基本方法
public void doSomething();
public void doAnything();
}
public class ConcreteImplementor1 implements Implementor {
@Override
public void doSomething() {
// 业务逻辑处理
}
@Override
public void doAnything() {
// 业务逻辑处理
}
}
public class ConcreteImplementor2 implements Implementor {
@Override
public void doSomething() {
// 业务逻辑处理
}
@Override
public void doAnything() {
// 业务逻辑处理
}
}
public abstract class Abstraction {
private Implementor implementor;
public Abstraction(Implementor implementor) {
this.implementor = implementor;
}
// 自身的行为和属性
public void request() {
this.implementor.doSomething();
}
// 获得实现化角色
public Implementor getImplementor() {
return implementor;
}
}
public class RefinedAbstraction extends Abstraction {
public RefinedAbstraction(Implementor implementor) {
super(implementor);
}
@Override
public void request() {
super.request();
super.getImplementor().doAnything();
}
}
public class Client {
public static void main(String[] args) {
// 定义一个实现化角色
Implementor implementor = new ConcreteImplementor1();
// 定义一个抽象化角色
Abstraction abstraction = new RefinedAbstraction(implementor);
// 执行行文
abstraction.request();
}
}
享元模式(Flyweight Pattern)
定义:使用共享对象可有效地支持大量的细粒度的对象。通用类图如下:
1-9 flyweight.png如何理解享元模式呢?
享元模式比较简单,可以大大减少应用程序创建的对象,降低程序内存占用,增强程序的性能,但它同时也提高了系统的复杂性,需要分离出外部状态和内部状态,而且外部状态具有固化特性,不应该随内部状态改变而改变,否则导致系统的逻辑混乱。
使用场景:系统中存在大量相似的对象;需要缓冲池的场景。
注意点:使用享元模式可以实现对象池,但是这两者还是有比较大的差异,对象池着重在对象的复用上,池中的每个对象是可替换的,从同一个池中获得A对象和B对象对客户端来说是完全相同的,它主要解决复用,而享元模式着重解决对象的共享问题,如何建立多个可共享的细粒度对象则是其关注的重点。
享元模式通用代码
public abstract class Flyweight {
// 内部状态
private String intrinsic;
// 外部状态
private final String extrinsic;
// 要求享元角色必须接受外部状态,外部状态可以理解为key,这样一个东西.
public Flyweight(String extrinsic) {
this.extrinsic = extrinsic;
}
public String getIntrinsic() {
return intrinsic;
}
public void setIntrinsic(String intrinsic) {
this.intrinsic = intrinsic;
}
// 定义业务操作
public abstract void operate();
}
public class ConcreteFlyweight1 extends Flyweight {
public ConcreteFlyweight1(String extrinsic) {
super(extrinsic);
}
// 根据外部状态进行逻辑处理
@Override
public void operate() {
// 业务逻辑处理
}
}
public class ConcreteFlyweight2 extends Flyweight {
public ConcreteFlyweight2(String extrinsic) {
super(extrinsic);
}
// 根据外部状态进行逻辑处理
@Override
public void operate() {
// 业务逻辑处理
}
}
public class FlyweightFactory {
// 定义一个池容器
private static HashMap<String, Flyweight> pool = new HashMap<String, Flyweight>();
// 享元工厂
public static Flyweight getFlyweight(String extrinsic) {
Flyweight flyweight = null;
if (pool.containsKey(extrinsic)) {
flyweight = pool.get(extrinsic);
}else {
// 根据外部状态创建享元对象
flyweight = new ConcreteFlyweight1(extrinsic);
// 放置到池中
pool.put(extrinsic, flyweight);
}
return flyweight;
}
}
代理模式VS装饰模式
首先要说的是装饰模式就是代理模式的一个特殊应用,两者的共同点是都具有相同的接口,不同点则是代理模式着重对代理过程的控制,而装饰模式则是对类的功能进行加强或减弱,它着重类的功能变化。
装饰模式VS适配器模式
相似点:都是包装作用,都是通过委托方式实现其功能。不同点是:装饰模式包装的是自己的兄弟类,隶属于同一个家族(相同接口或父类),适配器模式则修饰非血缘关系类,把一个非本家族的对象伪装成本家族的对象,注意是伪装,本质上还是非相同接口的对象。
国士梅花
欢迎大家关注国士梅花,技术路上与你陪伴。
guoshimeihua.jpg