包装设计展开图编写代码时,需要扩展一个类的功能吗?(组图)

导语

当你在编写代码时,需要扩展一个类的功能,或者是当前类的接口不能满足需求时,你会选择怎么做。重新编写子类,通过继承加入功能?修改原有类的接口使其符合现有环境?但你会发现这些改动是不完美的包装设计展开图,它们并不符合面向对象的「开放-关闭原则」。

开放-关闭原则: 对扩展开放,对修改关闭

在软件设计模式中有一个更好的答案——包装。

今天介绍的四种设计模式都围绕着“包装”展开,那么首先先简单了解一下这些设计模式:

装饰模式(Decorator Pattern)

当你想要扩展一个类的功能时,最直接的方法就是编写一个子类,然后在子类中加入新的功能函数。但是这种更改往往会导致很多问题,例如:想要去掉父类中的一个方法时。这不是一种弹性设计,不符合「开放-关闭原则」。装饰模式提供了继承之外的一种新思路。

什么是装饰模式?

定义:动态地将责任附加到对象上,给对象添加额外的职责,对于扩展功能来说, 装饰者提供了比继承更有弹性的方法。

装饰者&被装饰对象

装饰者和被装饰对象需要具有相同的父类,装饰者模式用继承达到类型匹配的目的

使用方法:将需要实例化的对象传入装饰类中进行包装

Java.IO库就是以装饰者模式来编写的 装饰模式代码实例[3]:

//Component定义了一个对象接口,通过装饰类可以给这些对象动态地添加职责
public abstract class Component {
    public abstract void operation();
}
/** Decorator,装饰抽象类,继承了Component
* 从外类来扩展Component类的功能,但对于Component来说,
* 是无需知道Decorator的存在的
*/
public abstract class Decorator extends Component {
		protected Component component;
//获取被装饰的对象
    public Component getComponent() {
	return component;
    }
//设置被装饰的对象
    public void setComponent(Component component) {
	this.component = component;
    }
    @Override
    public void operation() {
	if (component != null) {
    component.operation();
	}
    }
}
//具体装饰类,可以为类加入新的行为
class ConcreteDecoratorA extends Decorator {
    private String addedState;
    @Override
    public void operation() {
	// 首先运行原Component的operation(),再执行本类的功能,如addedState,相当于对原Component进行了装饰
	super.operation();
	addedState = "A中的new state ";
	System.out.println(addedState + "具体装饰对象A的操作");
    }
}
class ConcreteDecoratorB extends Decorator {
    @Override
    public void operation() {
	super.operation();
	addedBehavior();
	System.out.println("具体装饰对象B的操作");
    }
    public void addedBehavior() {
	System.out.print("B中的新增行为 ");
    }
}
class ConcreteDecoratorC extends Decorator {
    @Override
    public void operation() {
	super.operation();
	System.out.println("C没有特殊行为 " + "具体装饰对象C的操作");
    }
}
//ConcreteComponent是定义一个具体的对象,也可以给这个对象添加一些职责
public class ConcreteComponent extends Component {
    @Override
    public void operation() {
	System.out.println("具体对象的操作");
    }
}
//装饰模式客户端调用代码
public class DecoratorClient {
    public static void main(String[] args) {
	ConcreteComponent concreteComponent = new ConcreteComponent();
    //声明装饰类A、B、C
	ConcreteDecoratorA concreteDecoratorA = new ConcreteDecoratorA();
	ConcreteDecoratorB concreteDecoratorB = new ConcreteDecoratorB();
	ConcreteDecoratorC concreteDecoratorC = new ConcreteDecoratorC();
    //装饰的过程就像是层层包装,不断地装饰类包装对象,达到添加功能的目的
	concreteDecoratorA.setComponent(concreteComponent); //装饰类A包装对象
	concreteDecoratorB.setComponent(concreteDecoratorA); //装饰类B包装装饰类A(对象已经被包装在装饰类A中)
	concreteDecoratorC.setComponent(concreteDecoratorB); //装饰类C包装装饰类B
	concreteDecoratorC.operation();
    }
}

在DecoratorClient中,经过装饰类的包装后,最终对象关系如下,被装饰者concreteComponent包装在装饰类中,同时具有了各个装饰类附加的方法和行为

各个对象的包装关系装饰者模式和继承的区别:继承设计子类,是在编译时静态决定的,通过组合的做法扩展对象,可以在运行时动态地进行扩展装饰者模式通过组合和委托,可以在运行时动态地为对象加上新的行为装饰模式的优缺点优点:缺点:适配器模式(Adapter Pattern)

当系统的数据和行为都正确,需要使用一个现有类而其接口并不符合需求的时候,考虑用适配器,适配器模式主要应用于希望复用一些现存类,但是接口与复用环境不一致的情况。

什么是适配器模式?

定义:适配器模式将一个类的接口转换成客户期望的另一个接口,适配器让原本接口不兼容的类可以一起工作。

适配器通过使用对象组合,以修改的接口包装被适配者。

适配器把被适配者的接口转换为适配者的接口。

适用场景需要使用一个现有类而其接口并不符合需求的时候当两个类做的事情相同或相似,但具有不同接口时,可以使用适配器模式统一接口适配器模式代码实例[3]:

//客户所期待的接口
public abstract class Target {
    public void request() {
	System.out.println("普通请求!");
    }
}
//适配器类,通过在内部包装一个Adaptee对象,把原接口转换成目标接口
public class Adapter extends Target {
	//内部的Adaptee对象
    private Adaptee adaptee = new Adaptee();
    @Override
    public void request() {  //适配器类提供的客户端需要的接口
	adaptee.specificRequest();
    }
}
//需要适配的类
public class Adaptee {
    public void specificRequest() {
	System.out.println("特殊的请求!");
    }
}
//适配器客户端
public class AdapterClient {
    public static void main(String[] args) {
	Target target;
	target = new Adapter(); //直接调用适配器类,内部包装了Adaptee
	target.request();
    }
}

适配器模式优缺点优点:缺点: 适配器模式只有在碰到无法改变原有设计和代码的情况才考虑,在设计开发的过程中,应该预先预防接口不匹配的问题发生,当出现有小的接口不统一时,应及时重构,避免问题的扩大适配器模式的类型类适配器:继承被适配者类和目标类缺点: 对象适配器:利用组合的方式将请求传送给被适配者缺点: 与装饰者模式的区别:适配器模式:需要转换接口时使用 最少知识原则(Least Knowledge Principle)又名迪米特法则(Law of Demeter)

1. 每个单元对其他单元只拥有有限的知识,只了解与当前单元紧密联系的单元,即只和你的密友谈话

方针:只调用属于以下范围的方法

1. 该对象本身

2. 被当做方法的参数而传递进来的对象

3. 此方法所创建或实例化的任何对象

4. 对象的任何组件 外观模式(Facade Pattern)

当有很多复杂的接口需要使用时,通过一个类来将复杂的逻辑封装在内并提供简单的统一接口,可以很好的提高代码的可读性,降低程序复杂度。

外观模式就是这样的一个类,不过它并没有“封装”子系统。当你需要简化并统一一个很大的接口或一群复杂的接口时,可以使用外观模式。

什么是外观模式?

定义:外观模式提供了一个统一的接口,用来访问子系统中的一组接口。此模式定义了一个高层接口,使得子系统更容易使用。

适用场景

通过使用外观模式,在数据访问层、业务逻辑层和表示层的层与层之间建立“外观”,降低耦合度

外观模式代码实例[3]:

//“系统”接口,只是标记接口,暂无任何意义
public interface SystemInterface {
}
//子系统类
class SubSystemOne implements SystemInterface {
    public void methodOne() {
	System.out.println("子系统方法一");
    }
}
class SubSystemTwo implements SystemInterface {
    public void methodTwo() {
	System.out.println("子系统方法二");
    }
}
class SubSystemThree implements SystemInterface {
    public void methodThree() {
	System.out.println("子系统方法三");
    }
}
class SubSystemFour implements SystemInterface {
    public void methodFour() {
	System.out.println("子系统方法四");
    }
}
//外观类,包括了所有的子系统的方法或属性,进行组合,以备外界调用
public class Facade {
//子系统
    SubSystemOne subSystemOne;
    SubSystemTwo subSystemTwo;
    SubSystemThree subSystemThree;
    SubSystemFour subSystemFour;
    public Facade() {
	subSystemOne = new SubSystemOne();
	subSystemTwo = new SubSystemTwo();
	subSystemThree = new SubSystemThree();
	subSystemFour = new SubSystemFour();
    }
//外观类提供的统一接口
    public void methodA() {
	System.out.println("方法组A:");
	subSystemOne.methodOne();
	subSystemTwo.methodTwo();
	subSystemFour.methodFour();
    }
    public void methodB() {
	System.out.println("方法组B:");
	subSystemThree.methodThree();
	subSystemFour.methodFour();
    }
}
//外观类客户端
public class FacadeClient {
    public static void main(String[] args) {
	// 由于Facade的作用,客户端可以根本不知道四个子系统的存在
	Facade facade = new Facade();
	facade.methodA();
	facade.methodB();
    }
}

维护一个遗留的大型系统时,可能这个系统已经非常难以维护和扩展了,此时可以为新系统开发一个外观Facade类,来提供设计粗糙或高度复杂的遗留代码的比较清晰简单的接口包装设计展开图,让新系统与Facade对象交互,Facade与遗留代码交互所有复杂的工作。

外观模式优缺点:优点:缺点:与装饰模式、适配器模式的区别:代理模式(Proxy Pattern)

代理从字面意思理解就是代理他人的职务。在计算机中,代理常用来控制和管理访问,如代理服务器,可以作为中转站,代理网络用户去获取网络信息。

什么是代理模式

定义:代理模式为另一个对象提供一个替身或占位符以便控制客户对对象的访问

代理控制访问方式:远程代理:控制访问远程对象,为一个对象在不同的地理空间提供局部代表虚拟代理:控制访问创建开销大的资源,通过代理替代实例化需要很长时间的真实对象(网页加载时的图片框)保护代理:基于权限控制对资源的访问代理模式代码实例[3]:

这里的代理只是简单的示例,实际上的代理往往在上面提到的几个场景(远程、虚拟、保护)中使用,用来控制和管理访问

//接口类,定义真实实体类与代理类共用的接口
public interface Subject {
    public void request();
}
//实体类
public class RealSubject implements Subject {
    @Override
    public void request() {
	System.out.println("真实对象的请求");
    }
}
//代理类
public class Proxy implements Subject {
    // 保存一个引用,使得代理可以访问真实实体
    Subject subject;
    public Proxy() {
	subject = new RealSubject();
    }
    @Override
    public void request() {
	subject.request();
    }
}
//代理客户端
public class ProxyClient {
	public static void main(String[] args) {
	Proxy proxy = new Proxy();
	proxy.request();
    }
}

代理模式的优缺点:优点:缺点:与装饰模式、适配器模式的区别代理模式变体类型(简单了解)总结

对于装饰模式、适配器模式、外观模式和代理模式,他们彼此之间都有相似之处,例如四种都对对象进行了包装,适配器模式和外观模式都提供了接口,代理模式和装饰模式都可能会引入新功能......

但其实区分这几种设计模式重点不在于如何包装类,包装类的个数、是否添加新功能,重点在于不同设计模式的目的(意图)不同

四种设计模式的目的适配器模式:将一个对象包装起来以改变其接口 外观模式:将一群对象包装起来以简化其接口 代理模式:将一个对象包装起来以控制对它的访问

把握了以上几点,也就记住了四种设计模式的本质区别,相信对于每个设计模式适用场景也能有更深的理解。

Reference

[1] 程杰. 大话设计模式[M/OL]. 清华大学出版社 2007-12-1, 2007.

[2] FREEMAN E, FREEMAN E, BATES B, 等. Head First 设计模式(中文版)[M/OL]. TAIWAN公司 O, 译. 中国电力出版社, 2007.

[3] JavaCodeAcc 模式相关代码 /echoTheLiar/JavaCodeAcc/tree/master/src/designpattern/

[4] 适配器模式优缺点 /tayanxunhua/article/details/8373381

[5] 外观模式 design-patterns.readthedocs.io/zh_CN/latest/structural_patterns/facade.html#id10

[6] 代理模式优缺点 /designpattern/design-pattern-proxy.html

添加微信

转载原创文章请注明,转载自设计培训_平面设计_品牌设计_美工学习_视觉设计_小白UI设计师,原文地址:http://www.zfbbb.com/?id=13104

上一篇:杭州品牌设计公司 杭州·百艳本味蜂蜜品牌包装设计(组图)

下一篇:包装设计展开图【一年一度创意指南】包装与文创设计篇(组图)