Java 设计模式(二)
六、代理模式
本章介绍代理模式。
GoF 定义
为另一个对象提供代理或占位符,以控制对它的访问。
概念
代理基本上是预期对象的替代品。由于许多因素,访问原始对象并不总是可能的。例如,创建它的成本很高,它需要受到保护,它位于远程位置,等等。代理设计模式在类似的情况下可以帮助我们。当一个客户端处理一个代理对象时,它假设它正在与实际的对象进行对话。因此,在这种模式中,您可能希望使用一个可以作为其他东西的接口的类。
真实世界的例子
在教室里,当一个学生缺席时,他最好的朋友可能会在点名时试图模仿他的声音,以试图让他的朋友出席。
计算机世界的例子
在编程领域,创建一个复杂对象(重对象)的多个实例是很昂贵的。因此,只要有需要,就可以创建多个指向原始对象的代理对象。这种机制还可以帮助节省系统/应用程序内存。ATM 可以实现这种模式来保存可能存在于远程服务器上的银行信息的代理对象。
注意
在 java.lang.reflect 包中,可以有一个代理类和一个支持类似概念的 InvocationHandler 接口。 java.rmi.* 包还提供了一些方法,通过这些方法,一个 java 虚拟机上的对象可以调用驻留在不同 Java 虚拟机上的对象的方法。
说明
在下面的程序中,我调用了代理对象的doSomework()方法,而代理对象又调用了 ConcreteSubject 对象的doSomework()方法。当客户端看到输出时,他们不知道代理对象完成了任务。
类图
图 6-1 为类图。
图 6-1
类图
包资源管理器视图
图 6-2 显示了程序的高层结构。
图 6-2
包资源管理器视图
履行
下面是实现。
package jdp2e.proxy.demo;
// Abstract class Subject
abstract class Subject
{
public abstract void doSomeWork();
}
// ConcreteSubject class
class ConcreteSubject extends Subject
{
@Override
public void doSomeWork()
{
System.out.println("doSomeWork() inside ConcreteSubject is invoked.");
}
}
/**
* Proxy Class: It will try to invoke the doSomeWork()
* of a ConcreteSubject instance
*/
Class Proxy extends Subject
{
static Subject cs;
@Override
public void doSomeWork()
{
System.out.println("Proxy call happening now...");
//Lazy initialization:We'll not instantiate until the method is //called
if (cs == null)
{
cs = new ConcreteSubject();
}
cs.doSomeWork();
}
}
/**
* The client is talking to a ConcreteSubject instance
* through a proxy method.
*/
public class ProxyPatternExample {
public static void main(String[] args) {
System.out.println("***Proxy Pattern Demo***\n");
Proxy px = new Proxy();
px.doSomeWork();
}
}
输出
这是输出。
***Proxy Pattern Demo***
Proxy call happening now...
doSomeWork() inside ConcreteSubject is invoked.
问答环节
-
有哪些不同类型的代理?
These are the common types:
-
远程代理。隐藏位于不同地址空间的实际对象。
-
虚拟代理。执行优化技术,例如根据需求创建重物。
-
保护代理。处理不同的访问权限。
-
智能参考。当客户端访问对象时,执行额外的内务处理工作。典型的操作是计算在特定时刻对实际对象的引用次数。
-
-
You could create the ConcreteSubject instance in the proxy class constructor, as follows.
class Proxy extends Subject { static Subject cs; public Proxy() { //Instantiating inside the constructor cs = new ConcreteSubject(); } @Override public void doSomeWork() { System.out.println("Proxy call happening now..."); cs.doSomeWork(); } }这是正确的吗?
是的,你可以这样做。但是如果您遵循这种设计,无论何时实例化一个代理对象,您都需要实例化一个 ConcreteSubject 类的对象。所以,这个过程最终可能会产生不必要的对象。您可以简单地用下面这段代码和相应的输出进行测试。
替代实现
下面是替代实现。
package jdp2e.proxy.questions_answers;
//Abstract class Subject
abstract class Subject
{
public abstract void doSomeWork();
}
//ConcreteSubject class
class ConcreteSubject extends Subject
{
@Override
public void doSomeWork()
{
System.out.println("doSomeWork() inside ConcreteSubject is invoked");
}
}
/**
* Proxy Class
* It will try to invoke the doSomeWork() of a ConcreteSubject instance *
*/
class Proxy extends Subject
{
static Subject cs;
static int count=0;//A counter to track the number of instances
public Proxy()
{
//Instantiating inside the constructor
cs = new ConcreteSubject();
count ++;
}
@Override
public void doSomeWork()
{
System.out.println("Proxy call happening now...");
//Lazy initialization:We'll not instantiate until the method is //called
/*if (cs == null)
{
cs = new ConcreteSubject();
count ++;
}*/
cs.doSomeWork();
}
}
/**
* The client is talking to a ConcreteSubject instance
* through a proxy method.
*/
public class ProxyPatternQuestionsAndAnswers {
public static void main(String[] args) {
System.out.println("***Proxy Pattern Demo without lazy instantiation***\n");
//System.out.println("***Proxy Pattern Demo with lazy instantiation***\n");
Proxy px = new Proxy();
px.doSomeWork();
//2nd proxy instance
Proxy px2 = new Proxy();
px2.doSomeWork();
System.out.println("Instance Count="+Proxy.count);
}
}
没有惰性实例化的输出
这是输出。
***Proxy Pattern Demo without lazy instantiation***
Proxy call happening now...
doSomeWork() inside ConcreteSubject is invoked
Proxy call happening now...
doSomeWork() inside ConcreteSubject is invoked
Instance Count=2
分析
请注意,您已经创建了两个代理实例。
现在,用惰性实例化来尝试我们之前的方法。(删除代理构造函数,取消惰性实例化的注释)。
延迟实例化输出
这是输出。
***Proxy Pattern Demo with lazy instantiation***
Proxy call happening now...
doSomeWork() inside ConcreteSubject is invoked
Proxy call happening now...
doSomeWork() inside ConcreteSubject is invoked
Instance Count=1
分析
注意,这次您只创建了一个代理实例。
-
但是在这种 的懒惰实例化技术 中,你可能会在多线程应用中创建不必要的对象。这是正确的吗?
是的。在这本书里,我只展示简单的插图,所以我忽略了那部分。在关于单例模式的讨论中,我分析了一些处理多线程环境的替代方法。在这种情况下,你可以参考这些讨论。(例如,在这个特定的场景中,您可以实现同步技术、锁定机制或智能代理等等,以确保在授予对特定对象的访问权限之前锁定该对象。)
-
你能举一个远程代理的例子吗?
假设,你想调用一个对象的方法,但是该对象运行在不同的地址空间(例如,不同的位置或不同的计算机,等等。).你是如何进行的?在远程代理的帮助下,您可以调用代理对象上的方法,该方法又将调用转发给远程计算机上运行的实际对象。这种类型的需求可以通过众所周知的机制来实现,如 ASP.NET、CORBA、C# 的 WCF(3.0 版本以上)或 Java 的 RMI(远程方法调用)。
Figure 6-3 demonstrates a simple remote proxy structure.
图 6-3
一个简单的远程代理图
-
什么时候可以使用虚拟代理?
它可以用来避免多次加载一个非常大的图像。
-
什么时候可以使用保护代理?
组织中的安全团队可以实现保护代理来阻止对特定网站的互联网访问。
考虑下面的例子,它基本上是前面描述的代理模式实现的修改版本。为了简单起见,我们假设目前只有三个注册用户可以使用doSomeWork()代理方法。除此之外,如果任何其他用户(比如 Robin)试图调用该方法,系统将拒绝这些尝试。你必须同意,当系统将拒绝这种不必要的访问;制作代理对象毫无意义。因此,如果避免在代理类构造函数中实例化 ConcreteSubject 的对象,就可以很容易地避免创建这类额外的对象。
现在来看一下修改后的实现。
已修改的包资源管理器视图
图 6-4 显示了修改后的程序高层结构。
图 6-4
已修改的包资源管理器视图
修改的实现
下面是修改后的实现。
package jdp2e.proxy.modified.demo;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
//Abstract class Subject
abstract class Subject
{
public abstract void doSomeWork();
}
//ConcreteSubject class
class ConcreteSubject extends Subject
{
@Override
public void doSomeWork()
{
System.out.println("doSomeWork() inside ConcreteSubject is invoked.");
}
}
/**
* Proxy Class:It will try to invoke the doSomeWork()
* of a ConcreteSubject instance
*/
class ModifiedProxy extends Subject
{
static Subject cs;
String currentUser;
List<String> registeredUsers;
//Or, simply create this mutable list in one step
/*List<String> registeredUsers=new ArrayList<String>(Arrays.asList( "Admin","Rohit","Sam"));*/
public ModifiedProxy(String currentUser)
{
//Registered users are Admin, Rohit and Sam only
.
registeredUsers = new ArrayList<String>();
registeredUsers.add("Admin");
registeredUsers.add("Rohit");
registeredUsers.add("Sam");
this.currentUser = currentUser;
}
@Override
public void doSomeWork()
{
System.out.println("\n Proxy call happening now...");
System.out.println(currentUser+" wants to invoke a proxy method.");
if (registeredUsers.contains(currentUser))
{
//Lazy initialization:We'll not instantiate until the
//method is called
if (cs == null)
{
cs = new ConcreteSubject();
}
//Allow the registered user to invoke the method
cs.doSomeWork();
}
else
{
System.out.println("Sorry "+ currentUser+ " , you do not have access rights.");
}
}
}
/**
* The client is talking to a ConcreteSubject instance
* through a proxy method.
*/
public class ModifiedProxyPatternExample {
public static void main(String[] args) {
System.out.println("***Modified Proxy Pattern Demo***\n");
//Admin is an authorized user
ModifiedProxy px1 = new ModifiedProxy("Admin");
px1.doSomeWork();
//Robin is an unauthorized user
ModifiedProxy px2 = new ModifiedProxy("Robin");
px2.doSomeWork();
}
}
修改输出
这是修改后的输出。
-
代理就像装饰者一样。这是正确的吗?
您可以实现一个类似于 decorators 的保护代理,但是您不应该忘记它的意图。装饰者专注于增加职责,但是代理专注于控制对对象的访问。代理的类型和实现各不相同。另外,一般来说,代理在相同的接口上工作,但是装饰者可以在扩展的接口上工作。所以,如果你能记住他们的用途,在大多数情况下,你就能清楚地把他们和装修工区分开来。
-
代理人有哪些缺点?
If you are careful enough in your implementation, the pros are much greater than the cons, but
-
您可以提出您对响应时间的担忧。因为您没有直接与实际对象对话,所以通过这些代理的响应时间可能会更长。
-
您需要为代理维护额外的代码。
-
代理可以隐藏对象的实际响应,这在特殊情况下可能会造成混乱。
-
***Modified Proxy Pattern Demo***
Proxy call happening now...
Admin wants to invoke a proxy method.
doSomeWork() inside ConcreteSubject is invoked.
Proxy call happening now...
Robin wants to invoke a proxy method.
Sorry Robin, you do not have access rights.
七、装饰模式
本章涵盖了装饰模式。
GoF 定义
动态地将附加责任附加到对象上。Decorators 为扩展功能提供了子类化的灵活替代方案。
概念
这种模式说,类必须为修改而关闭,但为扩展而打开;也就是说,可以在不干扰现有功能的情况下添加新功能。当我们想给一个特定的对象而不是整个类添加特殊的功能时,这个概念非常有用。在这个模式中,我们尝试使用对象组合的概念来代替继承。所以,当我们掌握了这种技术,我们就可以在不影响底层类的情况下向对象添加新的职责。
真实世界的例子
假设你已经拥有一栋房子。现在你决定在它的上面再建一层楼。您可能不希望更改底层(或现有楼层)的体系结构,但可能希望在不影响现有体系结构的情况下更改新添加楼层的体系结构设计。
图 7-1 ,图 7-2 ,图 7-3 说明了这个概念。
图 7-3
从现有的装饰者中创建一个额外的装饰者(并粉刷房子)
图 7-2
有装修工的原始房屋(新结构建立在原始结构之上)
图 7-1
原始房屋
注意
情况 3 是可选的。您可以使用已经修饰过的对象来以这种方式增强行为,或者您可以创建一个新的 decorator 对象并将所有新的行为放入其中。
计算机世界的例子
假设在一个基于 GUI 的工具包中,我们想要添加一些边框属性。我们可以通过继承来做到这一点。但它不能被视为最终的解决方案,因为用户不能从一开始就对这种创造拥有绝对的控制权。所以在这种情况下核心选择是静态的。
装饰者以灵活的方式进入画面。他们提倡动态选择的概念,例如,我们可以将组件包围在另一个对象中。封闭对象被称为装饰器。它必须符合它所修饰的组件的接口。它将请求转发给组件。它可以在转发之前或之后执行附加操作。这个概念可以增加无限的责任。
注意
您可以注意到装饰模式在两者的 I/O 流实现中的使用。NET 框架和 Java。例如,java.io.BufferedOutputStream 类可以修饰任何 java.io.OutputStream 对象。
说明
浏览下面的例子。这里我们从未试图修改核心的makeHouse()方法。我们创建了两个额外的装饰器:ConcreteDecoratorEx1 和 ConcreteDecoratorEx2 来满足我们的需求,但是我们保持了原来的结构不变。
类图
图 7-4 显示了装饰器模式的类图。
图 7-4
类图
包资源管理器视图
图 7-5 显示了程序的高层结构。
图 7-5
包资源管理器视图
履行
下面是实现。
package jdp2e.decorator.demo;
abstract class Component
{
public abstract void makeHouse();
}
class ConcreteComponent extends Component
{
public void makeHouse()
{
System.out.println("Original House is complete. It is closed for modification.");
}
}
abstract class AbstractDecorator extends Component
{
protected Component component ;
public void setTheComponent(Component c)
{
component = c;
}
public void makeHouse()
{
if (component != null)
{
component.makeHouse();//Delegating the task
}
}
}
//A floor decorator
class FloorDecorator extends AbstractDecorator
{
public void makeHouse()
{
super.makeHouse();
//Decorating now.
System.out.println("***Floor decorator is in action***");
addFloor();
/*You can put additional stuffs as per your need*/
}
private void addFloor()
{
System.out.println("I am making an additional floor on top of it.");
}
}
//A paint decorator
class PaintDecorator extends AbstractDecorator
{
public void makeHouse()
{
super.makeHouse();
//Decorating now.
System.out.println("***Paint decorator is in action now***");
paintTheHouse();
//You can add additional stuffs as per your need
}
private void paintTheHouse()
{
System.out.println("Now I am painting the house.");
}
}
public class DecoratorPatternExample {
public static void main(String[] args) {
System.out.println("***Decorator pattern Demo***\n");
ConcreteComponent withoutDecorator = new ConcreteComponent();
withoutDecorator.makeHouse();
System.out.println("_________________");
//Using a decorator to add floor
System.out.println("Using a Floor decorator now.");
FloorDecorator floorDecorator = new FloorDecorator();
floorDecorator.setTheComponent(withoutDecorator);
floorDecorator.makeHouse();
System.out.println("_________________");
//Using a decorator to add floor to original house and then
//paint it.
System.out.println("Using a Paint decorator now.");
PaintDecorator paintDecorator = new PaintDecorator();
//Adding results from floor decorator
paintDecorator.setTheComponent(floorDecorator);
paintDecorator.makeHouse();
System.out.println("_________________");
}
}
输出
这是输出。
***Decorator pattern Demo***
Original House is complete. It is closed for modification.
_________________
Using a Floor decorator now.
Original House is complete. It is closed for modification.
***Floor decorator is in action***
I am making an additional floor on top of it.
_________________
Using a Paint decorator now.
Original House is complete. It is closed for modification.
***Floor decorator is in action***
I am making an additional floor on top of it.
***Paint decorator is in action now***
Now I am painting the house.
_________________
问答环节
图 7-6
等级继承
-
你能解释一下组合是如何促进一种动态行为,而继承却不能吗?
我们知道,当派生类从父类继承时,它只继承基类当时的行为。尽管不同的子类可以以不同的方式扩展基类/父类,但这种类型的绑定在编译时是已知的,所以选择本质上是静态的。但是您在示例中使用组合概念的方式允许您尝试动态行为。
当我们设计一个父类时,我们可能对客户在后期阶段可能想要什么样的额外责任没有足够的了解。我们的约束是我们不应该频繁地修改现有的代码。在这种情况下,对象组合不仅远远超过了继承,还确保了我们不会将错误引入到现有的架构中。
最后,在这种情况下,你必须记住一个关键的设计原则:类应该对扩展开放,但对修改关闭。
-
使用装潢师的关键 优势 是什么?
-
现有的结构没有被改动,所以您不会在那里引入错误。
-
新的功能可以很容易地添加到现有的对象。
-
您不需要在初始设计阶段预测/实现所有支持的功能。您可以增量开发(例如,一个接一个地添加装饰对象来支持增量需求)。你必须承认这样一个事实,如果你首先创建一个复杂的类,然后你试图扩展它的功能,这将是一个乏味的过程。
-
-
整体设计模式与继承有何不同?
您可以通过简单地附加或分离 decorators 来添加或删除职责。但是对于简单的继承机制,您需要为新的职责创建一个新的类。所以,有可能你最终会得到一个复杂的系统。
再次考虑这个例子。假设你想加一层新地板,粉刷房子,做一些额外的工作。为了满足这一需求,您从 decorator2 开始,因为它已经提供了向现有架构添加地板的支持,然后您可以对其进行绘制。因此,您可以添加一个简单的包装器来完成这些额外的职责。
但是如果你从继承开始,那么你可能有多个子类(例如,一个用于添加地板,一个用于粉刷房子)。图 7-6 显示了层次继承。
如果你需要一个具有额外功能的额外涂漆地板,你可能最终会得到如图 7-7 所示的设计。
图 7-7
一个类(额外特性)需要从多个基类继承
现在你感受到了钻石效应的热度,因为在包括 Java 在内的许多编程语言中,多个父类是不允许的。
在这种情况下,即使您考虑多级继承,您会发现总体上继承机制比装饰模式更具挑战性和耗时,并且它可能会在您的应用程序中产生重复的代码。最后,您必须记住,继承机制只提倡编译时绑定(而不是动态绑定)。
-
为什么多级 传承 不能在以前的背景下得分更高?
让我们假设油漆类是从附加层派生出来的,而附加层又是从核心架构派生出来的。现在,如果您的客户想在不增加地板的情况下粉刷房子,decorator 模式肯定优于继承机制,因为您可以简单地在现有的只支持粉刷的系统中添加一个 decorator。
-
为什么要创建一个只有单一职责的类?你可以创建一个子类,简单地添加一个地板,然后进行绘画。在这种情况下,你会得到更少的子类。这种理解正确吗?
如果你熟悉扎实的原理,你就知道有一个原理叫单责。这一原则背后的思想是,每个类都应该对软件中的一部分功能负责。当您使用单一责任原则时,装饰模式非常有效,因为您可以简单地动态添加/删除责任。
-
与此格局相关的 劣势 有哪些?
我相信,如果你足够小心的话,并没有显著的劣势。但是你必须意识到这样一个事实,如果你在系统中创建了太多的装饰器,那么维护和调试将会很困难。因此,在这种情况下,它会造成不必要的混乱。
-
在示例中,AbstractDecorator 类中没有抽象方法。这怎么可能?
在 Java 中,你可以拥有一个没有任何抽象方法的抽象类,但反之则不然;也就是说,如果一个类包含至少一个抽象方法,那么这个类本身就是不完整的,你必须用 abstract 关键字来标记它。
Let’s revisit the AbstractDecorator class in the comment shown in bold.
abstract class AbstractDecorator extends Component { protected Component component ; public void setTheComponent(Component c) { component = c; } public void makeHouse() { if (component != null) { component.makeHouse();//Delegating the task } } }您可以看到,我将任务委托给了一个具体的 decorator,因为我只想使用和实例化具体的 decorator。
同样,在这个例子中,您不能简单地实例化一个
AbstractDecorator实例,因为它用 abstract 关键字标记。The following line creates the Cannot instantiate the type AbstractDecorator compilation error.
AbstractDecorator abstractDecorator = new AbstractDecorator();
-
In your example, instead of using concrete decorators, you could use the concept of polymorphism in the following way to generate the same output.
System.out.println("Using a Floor decorator now."); //FloorDecorator floorDecorator = new FloorDecorator(); AbstractDecorator floorDecorator = new FloorDecorator(); floorDecorator.setTheComponent(withoutDecorator); floorDecorator.makeHouse(); //Using a decorator to add floor to original house and then paint //it. System.out.println("Using a Paint decorator now."); //PaintDecorator paintDecorator = new PaintDecorator(); AbstractDecorator paintDecorator = new PaintDecorator(); //Adding results from decorator1 paintDecorator.setTheComponent(floorDecorator); paintDecorator.makeHouse(); System.out.println("_________________");这是正确的吗?
是的。
-
只对动态绑定强制使用 decorators 吗?
不可以。静态和动态绑定都可以使用。但是动态绑定是它的强项,所以我集中在这上面。您可能会注意到,GoF 定义也只关注动态绑定。
-
你使用装饰者来包装你的核心架构。这是正确的吗?
是的。装饰器是扩展应用程序核心功能的包装器代码。但是当你使用它们的时候,核心架构是不变的。
八、适配器模式
本章介绍适配器模式。
GoF 定义
将类的接口转换成客户端期望的另一个接口。适配器允许类一起工作,否则由于不兼容的接口而无法工作。
概念
下面的例子最好地描述了核心概念。
真实世界的例子
这种模式在国际旅行中的电源插座适配器/交流电源适配器中非常常见。当接受美国电源的电子设备(比如笔记本电脑)可以插入欧洲电源插座时,这些适配器充当中间人。考虑另一个例子。假设你需要给手机充电,但是你看到总机和你的充电器不兼容。在这种情况下,您可能需要使用适配器。或者,在现实生活中,为某人翻译语言的译者可以被认为遵循了这种模式。
现在,您可以想象这样一种情况,您需要将一个应用程序插入一个适配器(在本例中是 X 形的)来使用预期的接口。如果不使用这个适配器,您就不能正确地连接应用程序和接口。
图 8-1 显示了使用适配器之前的情况。
图 8-1
在使用适配器之前
图 8-2 显示了使用适配器后的情况。
图 8-2
使用适配器后
计算机世界的例子
假设您有一个应用程序,可以大致分为两部分:用户界面(UI 或前端)和数据库(后端)。通过用户界面,客户端可以传递特定类型的数据或对象。您的数据库与那些对象兼容,可以顺利地存储它们。经过一段时间,你可能会觉得你需要升级你的软件来让你的客户满意。因此,您可能希望允许新类型的对象通过 UI。但是在这种情况下,第一个阻力来自你的数据库,因为它不能存储这些新类型的对象。在这种情况下,您可以使用一个适配器,负责将新对象转换为旧数据库可以接受的兼容形式。
注意
在 Java 中,可以将 java.io.InputStreamReader 类和 java.io.OutputStreamWriter 类视为对象适配器的示例。它们将现有的 InputStream/OutputStream 对象应用于读取器/写入器接口。您将很快了解到类适配器和对象适配器。
说明
下面的例子描述了这种模式的一个简单用法。
在这个例子中,你可以很容易地计算出一个矩形的面积。如果您注意到 Calculator 类和它的getArea()方法,您就会明白您需要在getArea()方法中提供一个 rectangle 对象来计算矩形的面积。现在假设你想计算一个三角形的面积,但是你的约束是你想通过计算器类的getArea()方法得到它的面积。那么,如何才能实现这一点呢?
为了处理这类问题,我为 Triangle 类制作了 CalculatorAdapter,并在它的getArea()方法中传递了一个三角形。反过来,该方法将三角形视为矩形,并从 Calculator 类的getArea()方法计算面积。
类图
图 8-3 显示了类图。
图 8-3
类图
包资源管理器视图
图 8-4 显示了程序的高层结构。
图 8-4
包资源管理器视图
履行
package jdp2e.adapter.demo;
class Rectangle
{
public double length;
public double width;
}
class Calculator
{
public double getArea(Rectangle rect)
{
return rect.length * rect.width;
}
}
class Triangle
{
public double base;//base
public double height;//height
public Triangle(int b, int h)
{
this.base = b;
this.height = h;
}
}
class CalculatorAdapter
{
public double getArea(Triangle triangle)
{
Calculator c = new Calculator();
Rectangle rect = new Rectangle();
//Area of Triangle=0.5*base*height
rect.length = triangle.base;
rect.width = 0.5 * triangle.height;
return c.getArea(rect);
}
}
class AdapterPatternExample {
public static void main(String[] args) {
System.out.println("***Adapter Pattern Demo***\n");
CalculatorAdapter calculatorAdapter = new CalculatorAdapter();
Triangle t = new Triangle(20,10);
System.out.println("Area of Triangle is " + calculatorAdapter.getArea(t) + " Square unit");
}
}
输出
这是输出。
***Adapter Pattern Demo***
Area of Triangle is 100.0 Square unit
修改后的插图
您已经看到了适配器设计模式的一个非常简单的例子。但是,如果你想严格遵循面向对象的设计原则,你可能想修改实现,因为你已经知道,而不是使用具体的类,你应该总是更喜欢使用接口。因此,记住这个关键原则,让我们修改实现。
修改的类图
修改后的实现的主要特征
以下是修改后的实现的主要特征。
-
rectangle 类实现 RectInterface,c
alculateAreaOfRectangle()方法帮助计算 Rectangle 对象的面积。 -
三角形类实现了 TriInterface,c
alculateAreaOfTriangle()方法帮助计算三角形对象的面积。 -
但是约束是您需要使用 RectInterface 计算三角形的面积(或者,您可以简单地说您现有的系统需要修改三角形对象)。为了达到这个目的,我引入了一个
adapter(TriangleAdapter),它与 RectInterface 接口交互。 -
矩形和三角形代码都不需要改变。您只是简单地使用适配器,因为它实现了 RectInterface 接口,并且使用 RectInterface 方法,您可以很容易地计算三角形的面积。这是因为我正在重写接口方法,以委托给我所适应的类(三角形)的相应方法。
-
注意
getArea(RectInterface)方法并不知道通过 TriangleAdapter,它实际上是在处理一个三角形对象而不是矩形对象。 -
注意另一个重要的事实和用法。假设在一个特定的情况下,您需要处理一些面积为 200 平方单位的矩形对象,但是您没有足够数量的这样的对象。但是你注意到你有面积为 100 平方单位的三角形物体。所以,使用这种模式,你可以调整一些三角形的物体。怎么做?好吧,如果你仔细观察,你会发现在使用适配器的
calculateAreaOfRectangle()方法时,你实际上是在调用一个三角形对象的calculateAreaOfTriangle()(即你是在委托你所适配的类的相应方法)。因此,您可以根据需要修改(覆盖)方法体(例如,在这种情况下,您可以将三角形面积乘以 2.0,得到 200 个平方单位的面积(就像一个长 20 个单位、宽 10 个单位的矩形对象)。
在您可能需要处理不完全相同但非常相似的对象的情况下,这种技术可以帮助您。在客户端代码的最后一部分,我展示了这样一种用法,应用程序使用增强的for循环(在 Java 5.0 中引入)显示系统中的当前对象。
注意
在最后一点的背景下,你必须同意,你不应该试图将一个圆转换成一个矩形(或类似类型的转换)来获得一个面积,因为它们是完全不同的。但在这个例子中,我谈论的是三角形和矩形,因为它们有一些相似之处,并且面积可以通过微小的变化很容易地计算出来。
已修改的包资源管理器视图
图 8-5 显示了修改后程序的结构。
图 8-5
已修改的包资源管理器视图
修改的实现
这是修改后的实现。
package jdp2e.adapter.modified.demo;
import java.util.ArrayList;
import java.util.List;
interface RectInterface
{
void aboutRectangle();
double calculateAreaOfRectangle();
}
class Rectangle implements RectInterface
{
public double length;
public double width;
public Rectangle(double length, double width)
{
this.length = length;
this.width = width;
}
@Override
public void aboutRectangle()
{
System.out.println("Rectangle object with length: "+ this.length +" unit and width :" +this.width+ " unit.");
}
@Override
public double calculateAreaOfRectangle()
{
return length * width;
}
}
interface TriInterface
{
void aboutTriangle();
double calculateAreaOfTriangle();
}
class Triangle implements TriInterface
{
public double base;//base
public double height;//height
public Triangle(double base, double height)
{
this.base = base;
this.height = height;
}
@Override
public void aboutTriangle() {
System.out.println("Triangle object with base: "+ this.base +" unit and height :" +this.height+ " unit.");
}
@Override
public double calculateAreaOfTriangle() {
return 0.5 * base * height;
}
}
/*TriangleAdapter is implementing RectInterface.
So, it needs to implement all the methods defined
in the target interface.*/
class TriangleAdapter implements RectInterface
{
Triangle triangle;
public TriangleAdapter(Triangle t)
{
this.triangle = t;
}
@Override
public void aboutRectangle() {
triangle.aboutTriangle();
}
@Override
public double calculateAreaOfRectangle() {
return triangle.calculateAreaOfTriangle();
}
}
class ModifiedAdapterPatternExample {
public static void main(String[] args) {
System.out.println("***Adapter Pattern Modified Demo***\n");
Rectangle rectangle = new Rectangle(20, 10);
System.out.println("Area of Rectangle is : "+ rectangle.calculateAreaOfRectangle()+" Square unit.");
Triangle triangle = new Triangle(10,5);
System.out.println("Area of Triangle is : "+triangle.calculateAreaOfTriangle()+ " Square unit.");
RectInterface adapter = new TriangleAdapter(triangle);
//Passing a Triangle instead of a Rectangle
System.out.println("Area of Triangle using the triangle adapter is : "+getArea(adapter)+" Square unit.");
//Some Additional code (Optional) to show the power of adapter
//pattern
List<RectInterface> rectangleObjects=new ArrayList<RectInterface>();
rectangleObjects.add(rectangle);
//rectangleObjects.add(triangle);//Error
rectangleObjects.add(adapter);//Ok
System.out.println("");
System.out.println("*****Current objects in the system are:******");
for(RectInterface rectObjects:rectangleObjects)
{
rectObjects.aboutRectangle();
}
}
/*getArea(RectInterface r) method does not know that through TriangleAdapter, it is getting a Triangle object instead of a Rectangle object*/
static double getArea(RectInterface r)
{
return r.calculateAreaOfRectangle();
}
}
修改输出
这是修改后的输出。
***Adapter Pattern Modified Demo***
Area of Rectangle is : 200.0 Square unit.
Area of Triangle is : 25.0 Square unit.
Area of Triangle using the triangle adapter is : 25.0 Square unit.
*****Current objects in the system are:******
Rectangle object with length: 20.0 unit and width :10.0 unit.
Triangle object with base: 10.0 unit and height :5.0 unit.
适配器的类型
GoF 解释了两种适配器:类适配器和对象适配器。
对象适配器
对象适配器通过对象组合进行适配,如图 8-6 所示。到目前为止讨论的适配器是对象适配器的一个例子。
图 8-6
典型的对象适配器
在我们的示例中,TriangleAdapter 是实现 RectInterface(目标接口)的适配器。三角形是适配器接口。适配器保存被适配器实例。
注意
因此,如果您遵循 TriangleAdapter 类的主体,您可以得出结论,要创建对象适配器,您需要遵循以下一般准则:
(1)您的类需要实现目标接口( 适配 接口)。如果目标是一个抽象类,你需要扩展它。
(2)在构造函数中提到你从 改编的 类,并在实例变量中存储对它的引用。
(3)重写接口方法,以委托您正在改编的类的相应方法。
类别适配器
类适配器通过子类化来适应。他们是多重继承的推动者。但是你知道在 Java 中,不支持通过类的多重继承。(你需要接口来实现多重继承的概念。)
图 8-7 显示了支持多重继承的类适配器的典型类图。
图 8-7
典型的类适配器
问答环节
-
如何用 Java 实现类适配器设计模式?
You can subclass an existing class and implement the desired interface. For example, if you want to use a class adapter instead of an object adapter in the modified implementation, then you can use the following code.
class TriangleClassAdapter extends Triangle implements RectInterface { public TriangleClassAdapter(double base, double height) { super(base, height); } @Override public void aboutRectangle() { aboutTriangle(); } @Override public double calculateAreaOfRectangle() { return calculateAreaOfTriangle(); } }但是请注意,您不能总是应用这种方法。例如,考虑一下 Triangle 类何时是最终类(因此,您不能从它派生)。除了这种情况,当您注意到您需要修改一个没有在接口中指定的方法时,您将再次被阻止。因此,在这种情况下,对象适配器是有用的。
-
“除了这种情况,当你注意到你需要修改一个没有在接口中指定的方法时,你将再次被阻止。”你这么说是什么意思?
In the modified implementation, you have used the
aboutRectangle()andaboutTriangle()methods .These methods are actually telling about the objects of the Rectangle and Triangle classes. Now, say, instead ofaboutTriangle() , there is a method calledaboutMe(), which is doing the same but there is no such method in the RectInterface interface. Then it will be a challenging task for you to adapt theaboutMe()method from the Triangle class and write code similar to this:for(RectInterface rectObjects:rectangleObjects) { rectObjects.aboutMe(); } -
你更喜欢哪一个——类适配器还是对象适配器?
在大多数情况下,我更喜欢组合而不是继承。对象适配器使用组合,更加灵活。此外,在许多情况下,您可能没有实现真正的类适配器。(在这种情况下,您可能会再次浏览前面问题的答案。)
-
这种模式有什么缺点?
我看不出有什么大的挑战。我相信您可以使适配器的工作简单明了,但是您可能需要编写一些额外的代码。但是回报是巨大的——特别是对于那些不能被改变但是你仍然需要使用它们来保持稳定性的遗留系统。
同时,专家建议您不要使用不同类型的验证或向适配器添加新的行为。理想情况下,adaptar 的工作应该仅限于执行简单的接口翻译。
九、外观模式
本章涵盖了外观模式。
GoF 定义
为子系统中的一组接口提供统一的接口。Facade 定义了一个更高级的接口,使得子系统更容易使用。
概念
门面使客户的生活更容易。假设有一个复杂的系统,其中多个对象需要执行一系列任务,你需要与系统进行交互。在这种情况下,facade 可以为您提供一个简化的接口,处理所有事情(创建那些对象,提供正确的任务序列,等等)。).结果,你不是以复杂的方式与多个对象交互,而是与单个对象交互。
这是支持松耦合的模式之一。在这里,您强调抽象,并通过公开一个简单的接口来隐藏复杂的细节。因此,代码变得更清晰,更有吸引力。
真实世界的例子
假设你要组织一个生日聚会,你打算邀请 500 人。现在,你可以去任何一个聚会组织者那里,让他们知道关键信息——聚会类型、日期和时间、参加人数等等。组织者会为您完成剩下的工作。你不需要考虑大厅将如何装饰,与会者是否会从自助餐桌上获得食物或由餐饮服务商提供服务,等等。所以,你不需要从商店里买东西或者自己装饰派对大厅——你只需要付钱给组织者,让他们把工作做好。
计算机世界的例子
考虑一种情况,您使用库中的方法(在编程语言的上下文中)。您不关心该方法在库中是如何实现的。您只需调用方法来试验它的简单用法。
注意
您可以有效地使用外观设计模式的概念,使您的 JDBC 应用程序更具吸引力。您可以将 java.net.URL 类视为门面模式实现的一个示例。考虑这个类中的简写 openStream()或 getContent()方法。openStream()方法返回 openConnection()。getInputStream()和 getContent()方法返回 openConnection.getContent() *。*在 URLConnection 类中进一步定义了 getInputStream()和 getContent()方法。
说明
在下面的实现中,您创建了一些机器人,然后销毁了这些对象。(在本例中,单词“destroy”没有用在垃圾收集的上下文中)。在这里,您可以通过调用 RobotFacade 类的constructMilanoRobot()和destroyMilanoRobot()这样的简单方法来构造或破坏特定类型的机器人。
从客户的角度来看,他/她只需要与 FacadePatternExample.java 进行交互。RobotFacade 全权负责创造或摧毁特定种类的机器人。这个 facade 与每个子系统(RobotHands、RobotBody、RobotColor)进行对话,以完成客户端的请求。RobotBody 类包括两个简单的静态方法,在创建或销毁机器人之前提供指令。
因此,在这个实现中,客户端不需要担心单独的类的创建和方法的调用顺序。
类图
图 9-1 显示了类图。
图 9-1
类图
包资源管理器视图
图 9-2 显示了程序的高层结构。
图 9-2
包资源管理器视图
履行
下面是实现。
// RobotBody.java
package jdp2e.facade.demo;
public class RobotBody
{
//Instruction manual -how to create a robot
public static void createRobot()
{
System.out.println(" Refer the manual before creation of a robot.");
}
//Method to create hands of a robot
public void createHands()
{
System.out.println(" Hands manufactured.");
}
//Method to create remaining parts (other than hands) of a robot
public void createRemainingParts()
{
System.out.println(" Remaining parts (other than hands) are created.");
}
//Instruction manual -how to destroy a robot
public static void destroyRobot()
{
System.out.println(" Refer the manual before destroying of a robot.");
}
//Method to destroy hands of a robot
public void destroyHands()
{
System.out.println(" The robot's hands are destroyed.");
}
//Method to destroy remaining parts (other than hands) of a robot
public void destroyRemainingParts()
{
System.out.println(" The robot's remaining parts are destroyed.");
}
}
//RobotColor.java
package jdp2e.facade.demo;
public class RobotColor
{
public void setDefaultColor()
{
System.out.println(" This is steel color robot.");
}
public void setGreenColor()
{
System.out.println(" This is a green color robot.");
}
}
// RobotHands.java
package jdp2e.facade.demo;
public class RobotHands
{
public void setMilanoHands()
{
System.out.println(" The robot will have EH1 Milano hands.");
}
public void setRobonautHands()
{
System.out.println(" The robot will have Robonaut hands.");
}
public void resetMilanoHands()
{
System.out.println(" EH1 Milano hands are about to be destroyed.");
}
public void resetRobonautHands()
{
System.out.println(" Robonaut hands are about to be destroyed.");
}
}
// RobotFacade.java
package jdp2e.facade.demo;
public class RobotFacade
{
RobotColor rColor;
RobotHands rHands ;
RobotBody rBody;
public RobotFacade()
{
rColor = new RobotColor();
rHands = new RobotHands();
rBody = new RobotBody();
}
//Constructing a Milano Robot
public void constructMilanoRobot()
{
RobotBody.createRobot();
System.out.println("Creation of a Milano Robot Start.");
rColor.setDefaultColor();
rHands.setMilanoHands();
rBody.createHands();
rBody.createRemainingParts();
System.out.println(" Milano Robot Creation End.");
System.out.println();
}
//Constructing a Robonaut Robot
public void constructRobonautRobot()
{
RobotBody.createRobot();
System.out.println("Initiating the creational process of a Robonaut Robot.");
rColor.setGreenColor();
rHands.setRobonautHands();
rBody.createHands();
rBody.createRemainingParts();
System.out.println("A Robonaut Robot is created.");
System.out.println();
}
//Destroying a Milano Robot
public void destroyMilanoRobot()
{
RobotBody.destroyRobot();
System.out.println(" Milano Robot's destruction process is started.");
rHands.resetMilanoHands();
rBody.destroyHands();
rBody.destroyRemainingParts();
System.out.println(" Milano Robot's destruction process is over.");
System.out.println();
}
//Destroying a Robonaut Robot
public void destroyRobonautRobot()
{
RobotBody.destroyRobot();
System.out.println(" Initiating a Robonaut Robot's destruction process.");
rHands.resetRobonautHands();
rBody.destroyHands();
rBody.destroyRemainingParts();
System.out.println(" A Robonaut Robot is destroyed.");
System.out.println();
}
}
//Client code
//FacadePatternExample.java
package jdp2e.facade.demo;
public class FacadePatternExample {
public static void main(String[] args) {
System.out.println("***Facade Pattern Demo***\n");
//Creating Robots
RobotFacade milanoRobotFacade = new RobotFacade();
milanoRobotFacade.constructMilanoRobot();
RobotFacade robonautRobotFacade = new RobotFacade();
robonautRobotFacade.constructRobonautRobot();
//Destroying robots
milanoRobotFacade.destroyMilanoRobot();
robonautRobotFacade.destroyRobonautRobot();
}
}
输出
这是输出。
***Facade Pattern Demo***
Refer the manual before creation of a robot.
Creation of a Milano Robot Start.
This is steel color robot.
The robot will have EH1 Milano hands.
Hands manufactured.
Remaining parts (other than hands) are created.
Milano Robot Creation End.
Refer the manual before creation of a robot.
Initiating the creational process of a Robonaut Robot.
This is a green color robot.
The robot will have Robonaut hands.
Hands manufactured.
Remaining parts (other than hands) are created.
A Robonaut Robot is created.
Refer the manual before destroying of a robot.
Milano Robot's destruction process is started.
EH1 Milano hands are about to be destroyed.
The robot's hands are destroyed.
The robot's remaining parts are destroyed.
Milano Robot's destruction process is over.
Refer the manual before destroying of a robot.
Initiating a Robonaut Robot's destruction process.
Robonaut hands are about to be destroyed.
The robot's hands are destroyed.
The robot's remaining parts are destroyed.
A Robonaut Robot is destroyed
.
问答环节
-
使用外观模式的主要优势是什么?
-
如果一个系统由许多子系统组成,管理所有这些子系统就变得非常困难,客户可能会发现很难与这些子系统中的每一个单独通信。在这种情况下,外观模式非常方便。它为客户提供了一个简单的界面。简而言之,您向客户呈现了一个简化的界面,而不是呈现复杂的子系统。这种方法还通过将客户端与子系统分离来促进弱耦合。
-
它还可以帮助您减少客户端需要处理的对象数量。
-
-
我看到 facade 类正在使用组合。这是故意的吗?
是的。使用这种方法,您可以很容易地访问每个子系统中的方法。
-
在我看来,外观并没有限制我们直接与子系统连接。这种理解正确吗?
是的。外观不封装子系统类或接口。它只是提供了一个简单的接口(或层)让你的生活更轻松。您可以自由地公开子系统的任何功能,但是在这些情况下,您的代码可能看起来很脏,同时,您会失去与该模式相关的所有好处。
-
它与适配器设计模式有何不同?
在适配器模式中,您试图改变一个接口,以便客户机感觉不到接口之间的差异。facade 模式简化了界面。它们为客户端提供了一个简单的交互界面(而不是复杂的子系统)。
-
一个复杂的子系统应该只有一个外观。这是正确的吗?
一点也不。您可以为特定子系统创建任意数量的外观。
-
我可以用一个门面添加更多的东西/逻辑吗?
是的,你可以。
-
与门面模式相关的挑战是什么?
-
子系统与外观层相连接。因此,您需要关注额外的编码层(即,您的代码库增加)。
-
当子系统的内部结构发生变化时,您也需要将变化合并到外观层中。
-
开发人员需要了解这个新层,而他们中的一些人可能已经知道如何有效地使用子系统/API。
-
-
它与 mediator 设计模式有什么不同?
在中介模式实现中,子系统知道中介。他们互相交谈。但是在外观中,子系统不知道外观,并且从外观到子系统提供单向通信。(本书第二十一章讨论了中介模式)。
-
在我看来,要实现一个门面模式,我必须写很多代码。这种理解正确吗?
一点也不。这取决于系统和相应的功能。例如,在前面的实现中,如果您只考虑一种类型的机器人(Milano 或 Robonaut),并且如果您不想提供机器人的销毁机制,并且如果您想忽略说明手册(在这个示例中是两个静态方法),您的代码大小将会显著下降。为了完整的说明,我保留了所有这些。
十、享元模式
这一章涵盖了 flyweight 模式。
GoF 定义
使用共享来有效地支持大量细粒度的对象。
概念
在他们的名著设计模式:可重用面向对象软件的元素 (Addison-Wesley,1995)中,四人组(g of)对享元做了如下描述:
flyweight 是一个可以同时在多个上下文中使用的共享对象。在每个上下文中,flyweight 充当一个独立的对象——它与未共享的对象实例没有区别。Flyweights 不能对它们运行的环境做出假设。
当你考虑享元模式时,你需要记住以下几点:
-
当您需要大量相似的对象时,这种模式非常有用,这些对象只有几个参数是唯一的,而大部分内容是通用的。
-
飞锤是一个物体。它试图通过与其他类似对象尽可能多地共享数据来最小化内存使用。共享对象可以允许以最小的成本在精细的粒度上使用它们。
-
在这种情况下使用两个常用术语:外在的和内在的。一个内在状态被存储/共享在 flyweight 对象中,并且它独立于 flyweight 的上下文。另一方面,外在状态随着 flyweight 的上下文而变化,这就是为什么它们不能被共享。客户端对象维护外在状态,它们需要将这种状态传递给一个 flyweight。请注意,如果需要,客户端也可以在使用 flyweights 时动态计算外部状态。
-
专家建议,在实现这种模式时,我们应该使固有状态不可变。
真实世界的例子
假设你有笔。可以更换不同的笔芯,用不同的颜色书写。因此,没有笔芯的笔被认为是具有内在数据的飞锤,而有笔芯的笔被认为是外在数据。
再考虑一个例子。假设一家公司需要为员工打印名片。那么,这个过程从哪里开始呢?该公司可以创建一个带有公司徽标、地址等的通用模板(内部),然后在卡片上添加每个员工的特定联系信息(外部)。
计算机世界的例子
假设您想创建一个网站,让不同的用户可以用他们喜欢的计算机语言(如 Java、C++、C# 等)编译和执行程序。如果你需要在短时间内为每个用户建立一个独特的环境,你的网站将会超负荷,服务器的响应时间将会变得非常慢,没有人会有兴趣使用你的网站。因此,您不必为每个用户创建一个新的编程环境,而是可以在他们之间创建一个公共的编程环境(支持不同的编程语言,有/没有微小的变化)。为了检查现有的/可用的编程环境,并决定是否需要创建一个新的环境,您可以维护一个工厂。
考虑另一个例子。假设在一个电脑游戏中,你有大量的参与者,他们的核心结构是相同的,但是他们的外表不同(例如,不同的状态、颜色、武器等等。)因此,假设您需要创建(或存储)所有这些具有所有这些变化/状态的对象,内存需求将是巨大的。因此,不用存储所有这些对象,您可以用这样的方式设计您的应用程序,即创建具有公共属性的这些实例(具有内在状态的 flyweights ),并且您的客户机对象维护所有这些变化(外在状态)。如果你能成功地实现这个概念,你就可以宣称你在应用程序中遵循了 flyweight 设计模式。
这种模式的另一个常见用途是在字处理器中用图形表示字符。
注意
在 Java 中,当您使用包装类(如 java.lang.Integer、java.lang.Short、java.lang.Byte 和 java.lang.Character)时,您可能会注意到这种模式的使用,其中静态方法 valueof()复制了一个工厂方法。(值得记住的是,一些包装器类,如 java.lang.Double 和 java.lang.Float,不遵循这种模式。)弦池是享元的另一个例子。
说明
在下面的例子中,我使用了三种不同类型的对象:小型、大型和固定大小的机器人。这些机器人有两种状态:“robotTypeCreated”和“color”。第一个可以在“相似”的对象之间共享,所以它是一个内在状态。第二个(颜色)由客户端提供,它随上下文而变化。因此,在这个例子中,它是一个非本征态。
对于固定尺寸的机器人,客户提供哪种颜色并不重要。对于这些机器人,我忽略了外在状态,所以你可以得出结论,这些固定大小的机器人代表了非共享享元。
在这个实现中,robotFactory 类缓存这些 flyweights,并提供一个获取它们的方法。
最后,这些对象是相似的。因此,一旦一个特定的机器人被创建,你不希望从头开始重复这个过程。相反,下一次向前,你将尝试使用这些飞锤来满足你的需求。现在,仔细阅读带有注释的代码,以便随时参考。
类图
图 10-1 为类图。
图 10-1
类图
包资源管理器视图
图 10-2 显示了程序的高层结构。
图 10-2
包资源管理器视图
履行
下面是实现。
package jdp2e.flyweight.demo;
import java.util.Map;
import java.util.HashMap;
import java.util.Random;
interface Robot
{
//Color comes from client.It is extrinsic.
void showMe(String color);
}
//A shared flyweight implementation
class SmallRobot implements Robot
{
/*
* Intrinsic state.
* It is not supplied by client.
* So, it is independent of the flyweight’s context.
* This can be shared across.
* These data are often immutable.
*/
private final String robotTypeCreated;
public SmallRobot()
{
robotTypeCreated="A small robot created";
System.out.print(robotTypeCreated);
}
@Override
public void showMe(String color)
{
System.out.print(" with " +color + " color");
}
}
//A shared flyweight implementation
class LargeRobot implements Robot
{
/*
* Intrinsic state.
* It is not supplied by client
.
* So, it is independent of the flyweight’s context.
* This can be shared across.
* These data are often immutable.
*/
private final String robotTypeCreated;
public LargeRobot()
{
robotTypeCreated="A large robot created";
System.out.print(robotTypeCreated);
}
@Override
public void showMe(String color)
{
System.out.print(" with " + color + " color");
}
}
//An unshared flyweight implementation
class FixedSizeRobot implements Robot
{
/*
* Intrinsic state.
* It is not supplied by client.
* So, it is independent of the flyweight’s context.
* This can be shared acorss.
*/
private final String robotTypeCreated;
public FixedSizeRobot()
{
robotTypeCreated="A robot with a fixed size created";
System.out.print(robotTypeCreated);
}
@Override
//Ingoring the extrinsic state argument
//Since it is an unshared flyweight
public void showMe(String color)
{
System.out.print(" with " + " blue (default) color");
}
}
class RobotFactory
{
static Map<String, Robot> robotFactory = new HashMap<String, Robot>();
public int totalObjectsCreated()
{
return robotFactory.size();
}
public static synchronized Robot getRobotFromFactory(String robotType) throws Exception
{
Robot robotCategory = robotFactory.get(robotType);
if(robotCategory==null)
{
switch (robotType)
{
case "small":
System.out.println("We do not have Small Robot at present.So we are creating a small robot now.")
;
robotCategory = new SmallRobot();
break;
case "large":
System.out.println("We do not have Large Robot at present.So we are creating a large robot now.");
robotCategory = new LargeRobot();
break;
case "fixed":
System.out.println("We do not have fixed size at present. So we are creating a fixed size robot now.");
robotCategory = new FixedSizeRobot();
break;
default:
throw new Exception(" Robot Factory can create only small ,large or fixed size robots");
}
robotFactory.put(robotType,robotCategory);
}
else
{
System.out.print("\n \t Using existing "+ robotType +" robot and coloring it" );
}
return robotCategory;
}
}
public class FlyweightPatternExample {
public static void main(String[] args) throws Exception {
RobotFactory robotFactory = new RobotFactory();
System.out.println("\n***Flyweight Pattern Example ***\n");
Robot myRobot;
//Here we are trying to get 3 Small type robots
for (int i = 0; i < 3; i++)
{
myRobot = RobotFactory.getRobotFromFactory("small");
/*
Not required to add sleep().But it is included to
increase the probability of getting a new random number
to see the variations in the output
.
*/
Thread.sleep(1000);
//The extrinsic property color is supplied by the client code.
myRobot.showMe(getRandomColor());
}
int numOfDistinctRobots = robotFactory.totalObjectsCreated();
System.out.println("\n Till now, total no of distinct robot objects created: " + numOfDistinctRobots);
//Here we are trying to get 5 Large type robots
for (int i = 0; i < 5; i++)
{
myRobot = RobotFactory.getRobotFromFactory("large");
/*
Not required to add sleep().But it is included to
increase the probability of getting a new random number
to see the variations in the output.
*/
Thread.sleep(1000);
//The extrinsic property color is supplied by the client code.
myRobot.showMe(getRandomColor());
}
numOfDistinctRobots = robotFactory.totalObjectsCreated();
System.out.println("\n Till now, total no of distinct robot objects created: " + numOfDistinctRobots);
//Here we are trying to get 4 fixed sizerobots
for (int i = 0; i < 4; i++)
{
myRobot = RobotFactory.getRobotFromFactory("fixed");
/*
Not required to add sleep().But it is included to
increase the probability of getting a new random number
to see the variations in the output.
*/
Thread.sleep(1000);
//The extrinsic property color is supplied by the client code.
myRobot.showMe(getRandomColor());
}
numOfDistinctRobots = robotFactory.totalObjectsCreated();
System.out.println("\n Till now, total no of distinct robot objects created: " + numOfDistinctRobots);
}
static String getRandomColor()
{
Random r = new Random();
/* I am simply checking the random number generated that can be either an even number or an odd number. And based on that we are choosing the color. For simplicity, I am using only two colors-red and green
*/
int random = r.nextInt();
if (random % 2 == 0)
{
return "red";
}
else
{
return "green";
}
}
}
输出
这是第一次运行输出。
***Flyweight Pattern Example ***
We do not have Small Robot at present.So we are creating a small robot now.
A small robot created with green color
Using existing small robot and coloring it with green color
Using existing small robot and coloring it with red color
Till now, total no of distinct robot objects created: 1
We do not have Large Robot at present.So we are creating a large robot now.
A large robot created with green color
Using existing large robot and coloring it with red color
Using existing large robot and coloring it with green color
Using existing large robot and coloring it with green color
Using existing large robot and coloring it with green color
Till now, total no of distinct robot objects created: 2
We do not have fixed size at present.So we are creating a fixed size robot now.
A robot with a fixed size created with blue (default) color
Using existing fixed robot and coloring it with blue (default) color
Using existing fixed robot and coloring it with blue (default) color
Using existing fixed robot and coloring it with blue (default) color
Till now, total no of distinct robot objects created: 3
这是第二次运行的输出。
***Flyweight Pattern Example ***
We do not have Small Robot at present.So we are creating a small robot now.
A small robot created with red color
Using existing small robot and coloring it with green color
Using existing small robot and coloring it with green color
Till now, total no of distinct robot objects created: 1
We do not have Large Robot at present.So we are creating a large robot now.
A large robot created with red color
Using existing large robot and coloring it with green color
Using existing large robot and coloring it with green color
Using existing large robot and coloring it with red color
Using existing large robot and coloring it with green color
Till now, total no of distinct robot objects created: 2
We do not have fixed size at present.So we are creating a fixed size robot now.
A robot with a fixed size created with blue (default) color
Using existing fixed robot and coloring it with blue (default) color
Using existing fixed robot and coloring it with blue (default) color
Using existing fixed robot and coloring it with blue (default) color
Till now, total no of distinct robot objects created: 3
分析
-
输出会有变化,因为在这个实现中,我随机选择了颜色。
-
固定大小机器人的颜色永远不会改变,因为外部状态(颜色)被忽略以表示非共享的享元。
-
客户需要与 12 个机器人(3 个小机器人,5 个大机器人,4 个固定大小的机器人)一起玩,但是这些需求只由三个不同的模板对象(每个类别一个)来满足,并且这些是动态配置的。
问答环节
-
我注意到了单例模式和享元模式之间的一些相似之处。你能突出他们之间的主要区别吗?
单例模式帮助您在系统中只维护一个必需的对象。换句话说,一旦创建了所需的对象,就不能再创建更多。您需要重用现有的对象。
flyweight 模式通常关注大量相似(可能很重)的对象,因为它们可能会占用很大的内存块。因此,您尝试创建一个较小的模板对象集,可以动态配置它来完成重对象的创建。这些更小的可配置对象被称为享元对象。您可以在应用程序中重用它们,以显示您有许多大型对象。这种方法有助于减少大块内存的消耗。基本上,享元让一个人看起来像很多人。这就是为什么 GoF 告诉我们:flyweight 是一个共享对象,可以同时在多个上下文中使用。flyweight 在每个上下文中充当一个独立的对象——它与没有共享的对象实例没有什么区别。
Figure 10-3 visualizes the core concepts of the flyweight pattern before using flyweights.
图 10-3
在使用飞锤之前
Figure 10-4 shows the design after using flyweights.
图 10-4
使用飞锤后
In Figure 10-4, you can see that
-
重对象 1 = Flyweight 对象(共享)+配置 1(非固有和非共享)
-
重对象 2 = Flyweight 对象(共享)+配置 2(非固有和非共享)
-
通过组合内部和外部状态,flyweight 对象提供了完整的功能。
-
您能观察到多线程带来的影响吗?
如果您在多线程环境中使用新的操作符创建对象,您可能最终会得到多个不需要的对象(类似于单例模式)。补救措施类似于在单例模式中处理多线程环境的方式。
-
使用 flyweight 设计模式的 优势 有哪些?
-
您可以减少可同等控制的重物的内存消耗。
-
您可以减少系统中“完全但相似的对象”的总数。
-
您可以提供一种集中的机制来控制许多“虚拟”对象的状态。
-
-
使用享元设计模式有哪些 挑战 ?
-
在这种模式中,您需要花时间来配置这些享元。配置时间会影响应用程序的整体性能。
-
要创建 flyweights,需要从现有对象中提取一个公共模板类。这个额外的编程层可能很棘手,有时很难调试和维护。
-
您可以看到,一个类的逻辑实例的行为不能与其他实例不同。
-
flyweight 模式通常与 singleton 工厂实现相结合,为了保护这种独特性,需要额外的成本(例如,您可以选择同步方法或双重检查锁定,但它们都是高成本的操作)。
-
-
我可以拥有不可共享的 flyweight 接口吗?
是的。flyweight 接口不会强制要求它总是可共享的。在某些情况下,您可能有不可共享的 flyweight,具体的 flyweight 对象作为子对象。在我们的例子中,您看到了使用固定大小的机器人来使用不可共享的享元。
-
由于 flyweights 的内在数据是相同的,我可以分享它们。这是正确的吗?
是的。
-
客户如何处理这些享元的外部数据?
他们需要将信息(状态)传递给享元。客户端要么管理数据,要么实时计算数据。
-
外部数据不可共享。这是正确的吗?
是的。
-
你说我应该努力使内在状态不变。我怎样才能做到这一点?
是的,为了线程安全,专家建议您实现它。在这种情况下,它已经实现了。在 Java 中,你必须记住字符串对象本质上是不可变的。
此外,您可能会注意到,在具体的 flyweights (SmallRobot、LargeRobot、FixedSizeRobot)中,没有 setter 方法来设置/修改 robotTypeCreated 的值。当您只通过构造函数提供数据并且没有 setter 方法时,您遵循的是一种促进不变性的方法。
-
您已经用固有状态 robotTypeCreated 标记了 final 关键字,以实现不变性。这是正确的吗?
你需要记住最终和不变性不是同义词。在设计模式的上下文中,单词不变性通常意味着一旦创建,就不能改变对象的状态。尽管关键字 final 可以应用于一个类、一个方法或一个字段,但目的是不同的。
final 字段可以帮助您构造一个线程安全的不可变对象,而无需同步,并且它在多线程环境中提供了安全性。所以,我在这个例子中使用了它。这个概念在
https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.5-110一文中有详细描述。 -
getRobotFromFactory()方法 在这里被同步以提供线程安全。这种理解正确吗?
没错。在单线程环境中,这不是必需的。 *** getRobotFromFactory()方法在这里是静态的。这是强制性的吗?
不,你也可以实现一个非静态的工厂方法。您可能经常会注意到 flyweight 模式实现的单例工厂的存在。
* **“机器人工厂”的** **在这个实现中的作用是什么?**
它缓存了 flyweights,并提供了获取它们的方法。在这个例子中,有许多可以共享的对象。因此,将它们存放在一个中心位置总是一个好主意。
**
十一、组合模式
本章涵盖了组合模式。
GoF 定义
将对象组成树结构来表示部分-整体层次结构。Composite 允许客户端统一处理单个对象和对象的组合。
概念
为了帮助你理解这个概念,我将从一个例子开始。考虑一家出售不同种类的干果和坚果的商店;比如腰果、枣和核桃。这些项目中的每一个都与特定的价格相关联。让我们假设你可以购买这些单独的物品,或者你可以购买包含不同物品的“礼包”(或盒装物品)。在这种情况下,数据包的开销是其组成部分的总和。组合模式在类似的情况下很有用,在这种情况下,您以相同的方式处理单个部分和部分的组合,以便可以统一处理它们。
这种模式对于表示对象的部分-整体层次结构很有用。在面向对象编程中,组合对象是由一个或多个相似对象组成的对象,其中每个对象都具有相似的功能。(这也称为对象之间的“有-有”关系)。这种模式在树状数据结构中非常常见。如果你能恰当地应用它,你就不需要区分分支和叶节点。使用这种模式可以实现两个关键目标。
-
您可以将对象组成一个树结构来表示部分-整体层次结构。
-
您可以统一访问组合对象(分支)和单个对象(叶节点)。因此,您可以降低代码的复杂性,同时使您的应用程序不容易出错。
真实世界的例子
你也可以想象一个由许多部门组成的组织。一般来说,一个组织有很多员工。这些员工中的一些被组合在一起形成一个部门,这些部门可以进一步组合在一起以构建组织的最终结构。
计算机世界的例子
任何树数据结构都可以遵循这个概念。客户可以用同样的方式对待树的叶子和非叶子(或树的分支)。
注意
这种模式常见于各种 UI 框架中。在 Java 中,通用抽象窗口工具包(AWT)容器对象是一个可以包含其他 AWT 组件的组件。例如,在 java.awt.Container 类(它扩展了 java.awt.Component)中,你可以看到 add(Component comp)方法的各种重载版本。在 JSF 中,UIViewRoot 类像一个组合节点,UIOutput 像一个叶节点。当你遍历一棵树时,你经常会用到迭代器设计模式,这将在第十八章中介绍。
说明
在这个例子中,我代表一个大学组织。让我们假设有一个校长和两个系主任——一个负责计算机科学与工程(CSE),一个负责数学(Maths)。数学系有两位老师(或教授/讲师),CSE 系有三位老师(或教授/讲师)。该组织的树形结构类似于图 11-1 。
图 11-1
一个样本大学组织
我们还假设在最后,CSE 部门的一个讲师退休了。您将在接下来的小节中研究所有这些情况。
类图
图 11-2 显示了类图。
图 11-2
类图
包资源管理器视图
图 11-3 显示了程序的高层结构。
图 11-3
包资源管理器视图
履行
下面是实现。
package jdp2e.composite.demo;
import java.util.ArrayList;
import java.util.List;
interface IEmployee
{
void printStructures();
int getEmployeeCount();
}
class CompositeEmployee implements IEmployee
{
//private static int employeeCount=0;
private int employeeCount=0;
private String name;
private String dept;
//The container for child objects
private List<IEmployee> controls;
//Constructor
public CompositeEmployee(String name, String dept)
{
this.name = name;
this.dept = dept;
controls = new ArrayList<IEmployee>();
}
public void addEmployee(IEmployee e)
{
controls.add(e);
}
public void removeEmployee(IEmployee e)
{
controls.remove(e);
}
@Override
public void printStructures()
{
System.out.println("\t" + this.name + " works in " + this.dept);
for(IEmployee e: controls)
{
e.printStructures();
}
}
@Override
public int getEmployeeCount()
{
employeeCount=controls.size();
for(IEmployee e: controls)
{
employeeCount+=e.getEmployeeCount();
}
return employeeCount;
}
}
class Employee implements IEmployee
{
private String name;
private String dept;
private int employeeCount=0;
//Constructor
public Employee(String name, String dept)
{
this.name = name;
this.dept = dept;
}
@Override
public void printStructures()
{
System.out.println("\t\t"+this.name + " works in " + this.dept);
}
@Override
public int getEmployeeCount()
{
return employeeCount;//0
}
}
class CompositePatternExample {
/**Principal is on top of college.
*HOD -Maths and Comp. Sc directly reports to him
*Teachers of Computer Sc. directly reports to HOD-CSE
*Teachers of Mathematics directly reports to HOD-Maths
*/
public static void main(String[] args) {
System.out.println("***Composite Pattern Demo ***");
//2 teachers other than HOD works in Mathematics department
Employee mathTeacher1 = new Employee("Math Teacher-1","Maths");
Employee mathTeacher2 = new Employee("Math Teacher-2","Maths");
//teachers other than HOD works in Computer Sc. Department
Employee cseTeacher1 = new Employee("CSE Teacher-1", "Computer Sc.");
Employee cseTeacher2 = new Employee("CSE Teacher-2", "Computer Sc.");
Employee cseTeacher3 = new Employee("CSE Teacher-3", "Computer Sc.");
//The College has 2 Head of Departments-One from Mathematics, One //from Computer Sc.
CompositeEmployee hodMaths = new CompositeEmployee("Mrs.S.Das(HOD-Maths)","Maths");
CompositeEmployee hodCompSc = new CompositeEmployee("Mr. V.Sarcar(HOD-CSE)", "Computer Sc.");
//Principal of the college
CompositeEmployee principal = new CompositeEmployee("Dr.S.Som(Principal)","Planning-Supervising-Managing");
//Teachers of Mathematics directly reports to HOD-Maths
hodMaths.addEmployee(mathTeacher1);
hodMaths.addEmployee(mathTeacher2);
//Teachers of Computer Sc. directly reports to HOD-CSE
hodCompSc.addEmployee(cseTeacher1);
hodCompSc.addEmployee(cseTeacher2);
hodCompSc.addEmployee(cseTeacher3);
/*Principal is on top of college.HOD -Maths and Comp. Sc directly reports to him*/
principal.addEmployee(hodMaths);
principal.addEmployee(hodCompSc);
/*Printing the leaf-nodes and branches in the same way i.e.
in each case, we are calling PrintStructures() method
*/
System.out.println("\n Testing the structure of a Principal object");
//Prints the complete structure
principal.printStructures();
System.out.println("At present Principal has control over "+ principal.getEmployeeCount()+ " number of employees.");
System.out.println("\n Testing the structure of a HOD-CSE object:");
//Prints the details of Computer Sc, department
hodCompSc.printStructures();
System.out.println("At present HOD-CSE has control over "+ hodCompSc.getEmployeeCount()+ " number of employees.");
System.out.println("\n Testing the structure of a HOD-Maths object:");
//Prints the details of Mathematics department
hodMaths.printStructures();
System.out.println("At present HOD-Maths has control over "+ hodMaths.getEmployeeCount()+ " number of employees.");
//Leaf node
System.out.println("\n Testing the structure of a leaf node:");
mathTeacher1.printStructures();
System.out.println("At present Math Teacher-1 has control over "+ mathTeacher1.getEmployeeCount()+ " number of employees.");
/*Suppose, one computer teacher is leaving now
from the organization*/
hodCompSc.removeEmployee(cseTeacher2);
System.out.println("\n After CSE Teacher-2 resigned, the organization has following members:");
principal.printStructures();
System.out.println("At present Principal has control over "+ principal.getEmployeeCount()+ " number of employees");
System.out.println("At present HOD-CSE has control over "+ hodCompSc.getEmployeeCount()+ " number of employees");
System.out.println("At present HOD-Maths has control over "+ hodMaths.getEmployeeCount()+ " number of employees");
}
}
输出
这是输出。关键变化以粗体显示。
***Composite Pattern Demo ***
Testing the structure of a Principal object
Dr.S.Som(Principal) works in Planning-Supervising-Managing
Mrs.S.Das(HOD-Maths) works in Maths
Math Teacher-1 works in Maths
Math Teacher-2 works in Maths
Mr. V.Sarcar(HOD-CSE) works in Computer Sc.
CSE Teacher-1 works in Computer Sc.
CSE Teacher-2 works in Computer Sc.
CSE Teacher-3 works in Computer Sc.
At present Principal has control over 7 number of employees.
Testing the structure of a HOD-CSE object:
Mr. V.Sarcar(HOD-CSE) works in Computer Sc.
CSE Teacher-1 works in Computer Sc.
CSE Teacher-2 works in Computer Sc.
CSE Teacher-3 works in Computer Sc.
At present HOD-CSE has control over 3 number of employees.
Testing the structure of a HOD-Maths object:
Mrs.S.Das(HOD-Maths) works in Maths
Math Teacher-1 works in Maths
Math Teacher-2 works in Maths
At present HOD-Maths has control over 2 number of employees.
Testing the structure of a leaf node:
Math Teacher-1 works in Maths
At present Math Teacher-1 has control over 0 number of employees.
After CSE Teacher-2 resigned, the organization has following members:
Dr.S.Som(Principal) works in Planning-Supervising-Managing
Mrs.S.Das(HOD-Maths) works in Maths
Math Teacher-1 works in Maths
Math Teacher-2 works in Maths
Mr. V.Sarcar(HOD-CSE) works in Computer Sc.
CSE Teacher-1 works in Computer Sc.
CSE Teacher-3 works in Computer Sc.
At present Principal has control over 6 number of employees
At present HOD-CSE has control over 2 number of employees
At present HOD-Maths has control over 2 number of employees
问答环节
-
使用组合设计模式的 优势 有哪些?
-
在树状结构中,您可以统一处理组合对象(分支)和单个对象(叶节点)。注意,在这个例子中,我使用了两个常用的方法:pr
intStructures()和getEmployeeCount()来打印结构,并从组合对象结构(principal 或 hods)和单个对象结构(例如,像 Math Teacher 1 这样的叶节点)中获取雇员数。) -
使用这种设计模式实现部分-整体层次结构是非常常见的。
-
您可以轻松地向现有架构添加新组件,或者从您的架构中删除现有组件。
-
-
与使用组合设计模式相关的 挑战 有哪些?
-
如果您想要保持子节点的顺序(例如,如果解析树被表示为组件),您可能需要付出额外的努力。
-
如果你正在处理不可变的对象,你不能简单地删除它们。
-
您可以轻松地添加一个新组件,但是这种支持会导致将来的维护开销。有时,您想要处理具有特殊组件的组合对象。这种约束会导致额外的开发成本,因为您可能需要实现一个动态检查机制来支持这个概念。
-
-
在这个例子中,你使用了列表数据结构 。但是我更喜欢使用其他数据结构。这样可以吗?
绝对的。没有放之四海而皆准的规则。您可以自由使用您喜欢的数据结构。GoF 确认没有必要使用任何通用数据结构*。*
-
如何将迭代器设计模式连接到组合设计模式?
再看一遍我们的例子。如果您想要检查组合对象架构,您可能需要迭代对象。换句话说,如果你想用分支做特殊的活动,你可能需要迭代它的叶节点和非叶节点。迭代器模式通常与组合模式一起使用。
-
在您的实现的接口中,您只定义了两个方法:printStructures() 和 getEmployeeCount() 。但是在组合类(CompositeEmployee)中使用其他方法添加和移除对象。为什么没有把这些方法放到接口里?
不错的观察。GoF 对此进行了讨论。让我们看看如果在接口中放入
addEmployee (…)和removeEmployee (…)方法会发生什么。叶节点需要实现添加和删除操作。但是这种情况下会有意义吗?显而易见的答案是否定的。看起来你失去了透明性,但我相信你有更多的安全性,因为我已经阻止了叶节点中无意义的操作。这就是为什么 GoF 提到这种决定涉及到安全性和透明度之间的权衡。 -
我想用抽象类代替接口。这是允许的吗?
In most of the cases, the simple answer is yes. But you need to understand the difference between an abstract class and an interface. In a typical scenario, you find one of them more useful than the other one. Since I am presenting only simple and easy to understand examples, you may not see much difference between the two. Particularly in this example, if I use the abstract class instead of the interface, I may put a default implementation of
getEmployeeCount()in the abstract class definition. Although you can still argue that with Java’s default keyword, you could achieve the same, as in the following:interface IEmployee { void printStructures(); //int getEmployeeCount(); default public int getEmployeeCount() { return 0; } }
注意
在 builder 模式的问答环节(见第三章),我讨论了如何在抽象类和接口之间做出决定。