代理模式
在有些情况下,一个客户不能或者不想直接访问另一个对象,这时需要找一个中介帮忙完成某项任务,这个中介就是代理对象。例如,购买火车票不一定要去火车站买,可以通过 12306 网站或者去火车票代售点购买。又如找女朋友、找保姆、找工作都可以通过中介完成。
在软件设计中,使用代理模式的例子很多,如,要访问原型对象比较大(如视频或者大图像等),其下载要花很多时间。还有因为安全需要屏蔽客户端直接访问真实对象,如某单位的内部数据库等。
定义与特点
代理模式的定义:由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不合适或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
代理模式的主要优点:
- 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
- 代理模式可以扩展目标对象的功能;
- 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度。
其主要缺点是:
- 在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢;
- 增加了系统的复杂度。
代理模式分为三类:1. 静态代理 2. 动态代理 3. CGLIB代理
结构与实现
代理模式的结构比较简单,只要是通过定义一个集成抽象主题的代理来包含真实主题,从而实现对真实主题访问,下面来分析基本结构和实现方法。
(1)模式的结构
代理模式的主要角色如下:
1)抽象主题类:通过接口或抽象类声明真实主题或代理对象实现的业务方法。
2)真实主题类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要 引用的对象。
3)代理类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。
其结构图如下图 所示:
(2)模式的实现
代理模式的实现代码如下:
// 抽象主题
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();
postRequest();
}
public void preRequest() {
System.out.println("访问真实主题之前的预处理。");
}
public void postRequest() {
System.out.println("访问真实主题之后的后续处理。");
}
}
public class ProxyTest {
public static void main(String[] args) {
Proxy proxy = new Proxy();
proxy.Request();
}
}
程序运行的结果如下:
访问真实主题之前的预处理。
访问真实主题方法...
访问真实主题之后的后续处理。
静态代理
举例(买房),类图如下:
第一步:创建服务类接口
public interface BuyHouse {
void buyHosue();
}
第二步:实现服务接口
public class BuyHouseImpl implements BuyHouse {
@Override
public void buyHosue() {
System.out.println("我要买房");
}
}
第三步:创建代理类
public class BuyHouseProxy implements BuyHouse {
private BuyHouse buyHouse;
public BuyHouseProxy(final BuyHouse buyHouse) {
this.buyHouse = buyHouse;
}
@Override
public void buyHosue() {
System.out.println("买房前准备");
buyHouse.buyHosue();
System.out.println("买房后装修");
}
}
总结:
优点:可以做到在符合开闭原则的情况下对目标对象进行功能扩展。
缺点: 代理对象与目标对象要实现相同的接口,我们得为每一个服务都得创建代理类,工作量太大,不易管理。同时接口一旦发生改变,代理类也得相应修改。
动态代理
动态代理有以下特点:
-
1.代理对象,不需要实现接口
-
2.代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)
(代理类不用再实现接口了。但是,要求被代理对象必须有接口。)
动态代理实现:
Java.lang.reflect.Proxy类可以直接生成一个代理对象
-
Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)生成一个代理对象
-
参数1:ClassLoader loader 代理对象的类加载器 一般使用被代理对象的类加载器
-
参数2:Class<?>[] interfaces 代理对象的要实现的接口 一般使用的被代理对象实现的接口
-
参数3:InvocationHandler h (接口)执行处理类
-
-
InvocationHandler中的invoke(Object proxy, Method method, Object[] args)方法:调用代理类的任何方法,此方法都会执行
-
参数1:代理对象(慎用)
-
参数2:当前执行的方法
-
参数3:当前执行的方法运行时传递过来的参数
-
第一步:编写动态处理器
public class DynamicProxyHandler implements InvocationHandler {
private Object object;
public DynamicProxyHandler(final Object object) {
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("买房前准备");
Object result = method.invoke(object, args);
System.out.println("买房后装修");
return result;
}
}
第二步:编写测试类
public class DynamicProxyTest {
public static void main(String[] args) {
BuyHouse buyHouse = new BuyHouseImpl();
BuyHouse proxyBuyHouse = (BuyHouse) Proxy.newProxyInstance(BuyHouse.class.getClassLoader(), new
Class[]{BuyHouse.class}, new DynamicProxyHandler(buyHouse));
proxyBuyHouse.buyHosue();
}
}
动态代理总结:虽然相对于静态代理,动态代理大大减少了我们的开发任务,同时减少了对业务接口的依赖,降低了耦合度。
但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持interface代理的桎梏(我们要使用被代理的对象的接口),因为它的设计注定了这个遗憾。
CGLIB代理
CGLIB 原理:动态生成一个要代理类的子类,子类重写要代理的类的所有不是final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。它比使用java反射的JDK动态代理要快。
CGLIB 底层:使用字节码处理框架ASM,来转换字节码并生成新的类。不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。
CGLIB缺点:对于final方法,无法进行代理。
CGLIB的实现步骤:
第一步:建立拦截器
public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("买房前准备");
Object result = methodProxy.invoke(object, args);
System.out.println("买房后装修");
return result;
}
参数:Object为由CGLib动态生成的代理类实例,Method为上文中实体类所调用的被代理的方法引用,Object[]为参数值列表,MethodProxy为生成的代理类对方法的代理引用。
返回:从代理实例的方法调用返回的值。
其中,proxy.invokeSuper(obj,arg) 调用代理类实例上的proxy方法的父类方法(即实体类TargetObject中对应的方法)
第二步: 生成动态代理类
public class CglibProxy implements MethodInterceptor {
private Object target;
public Object getInstance(final Object target) {
this.target = target;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.target.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("买房前准备");
Object result = methodProxy.invoke(object, args);
System.out.println("买房后装修");
return result;
}
}
这里Enhancer类是CGLib中的一个字节码增强器,它可以方便的对你想要处理的类进行扩展,以后会经常看到它。
首先将被代理类TargetObject设置成父类,然后设置拦截器TargetInterceptor,最后执行enhancer.create()动态生成一个代理类,并从Object强制转型成父类型TargetObject。
第三步:测试
public class CglibProxyTest {
public static void main(String[] args){
BuyHouse buyHouse = new BuyHouseImpl();
CglibProxy cglibProxy = new CglibProxy();
BuyHouseImpl buyHouseCglibProxy = (BuyHouseImpl) cglibProxy.getInstance(buyHouse);
buyHouseCglibProxy.buyHosue();
}
}
CGLIB代理总结: CGLIB创建的动态代理对象比JDK创建的动态代理对象的性能更高,但是CGLIB创建代理对象时所花费的时间却比JDK多得多。
所以对于单例的对象,因为无需频繁创建对象,用CGLIB合适,反之使用JDK方式要更为合适一些。同时由于CGLib由于是采用动态创建子类的方法,对于final修饰的方法无法进行代理。
应用场景
● 远程代理,这种方法通常是为了隐藏目标对象目标存在于不同地址空间的事实,方便客户端访问。例如,用户申请某些网盘空间时,会在用户的文件系统中建立一个虚拟的硬盘,用户访问虚拟硬盘时实际访问的是网盘空间。
● 虚拟代理,这种方式通常用于要创建的目标对象开销很大。例如,下载一个很大的图片需要很长时间,因某种计算比较复杂而短时间无法完成,这时可以先用小比例的虚拟代理替换真实的对象,消除用户对服务器慢的感觉。
● 安全代理,这种方式通常用于控制不同种类客户对真实对象的访问权限。
● 智能指引,主要用于调用目标对象时,代理附加一些额外的处理功能。例如,增加计算真实对象的引用次数的功能,这样当该对象没有被引用时,就可以自动释放它。
● 延迟加载,指为了提高系统的性能,延迟对目标的加载。例如,Hibernate 中就存在属性的延迟加载和关联表的延时加载。
适配器模式
在现实生活中,经常出现两个对象因接口不兼容而不能再一起工作的实例,这时需要第三者进行适配。
例如,讲中文的人同讲英文的人对话时需要一个翻译,用直流电的笔记本电脑接交流电源时需要一个电源适配器。在软件设计中也可能出现:需要开发的具有某种业务功能的组件在现有的组件库中已经存在,但它们与当前系统的接口规范不兼容,如果重写开发这些组件成本又很高,这时用是适配器模式能很好地解决这些问题。
定义与特点
将一个类的接口转换成客户希望的另一个接口,使得原本由于接口不兼容而不能一起功能的那些类能一起工作。
适配器模式分为类结构型模式和对象结构型模式两种,前者之间的耦合度比后者高,其要求程序员了解现有组件库中的相关的内部结构,所以应对相对较少些。
该模式的主要优点如下:
● 客户端通过适配器可以透明地调用目标接口。
● 复用了现存的类,程序员不需要修改原有代码而重用现有的适配者类。
● 将目标和适配这类解耦,解决了目标类和适配类接口不一致问题。
其缺点是:
● 对类适配器来说,更换适配器的实现过程比较复杂。
结构与实现
类适配器模式可采用多重继承方式实现,如 C++ 可以定义一个适配器类来同时继承房钱系统的业务接口和现有组件库中已经存在的组件接口;
Java 不支持多继承,但可以定义一个适配器来来实现当前系统的业务接口,同时又继承现有组件库中已经存在的组件。
对象适配器模式可采用将现有组件库中已经实现的组件引入适配器类,该类同时实现当前系统的业务接口。现在来介绍他们的基本结构。
(1)模式的结构
适配器模式包含以下主要角色。
1)目标接口:当前系统业务所期待的接口,它可以是抽象类或接口。
2)适配类:它是被访问和适配的现存组件库中的组件接口。
3)适配器类:它是一个转换器,通过继承或引用适配者的对象,把适配器接口转换成目标接口,让客户目标接口的可是访问适配者。
类适配器模式的结构图,如下图所示:
对象适配器模式的结构图,如下图所示:
(2)模式的实现
// 目标接口
interface Target {
public void request();
}
① 类适配器模式的代码如下:
// 适配者接口
class Adaptee {
public void specificRequest() {
System.out.println("适配者中的业务代码被调用!");
}
}
// 类适配器类
class ClassAdapter extends Adaptee implements Target {
public void request() {
specificRequest();
}
}
//客户端代码
public class ClassAdapterTest {
public static void main(String[] args) {
System.out.println("类适配器模式测试:");
Target target = new ClassAdapter();
target.request();
}
}
程序运行结果如下:
类适配器模式测试:
适配者中的业务代码被调用!
② 对象适配器模式的代码如下:
// 对象适配器类
class ObjectAdapter implements Target {
private Adaptee adaptee;
public ObjectAdapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
public void request() {
adaptee.specificRequest();
}
}
// 客户端代码
public class ObjectAdapterTest {
public static void main(String[] args) {
System.out.println("对象适配器模式测试:");
Adaptee adaptee = new Adaptee();
Target target = new ObjectAdapter(adaptee);
target.request();
}
}
程序运行结果如下:
对象适配器模式测试:
适配者中的业务代码被调用!
说明:对象适配器模式中的“目标接口”和“适配者类”的代码同类适配器模式一样,只要修改适配器类和客户端的代码即可。
③ 接口适配器模式的代码如下:
当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求,它适用于一个接口不想使用其所有的方法的情况。
public abstract class AdapterUSB2VGA implements VGA {
USB u = new USBImpl();
@Override
public void projection() {
u.showPPT();
}
@Override
public void b() {
};
@Override
public void c() {
};
}
AdapterUSB2VGA实现,不用去实现b()和c()方法。
public class AdapterUSB2VGAImpl extends AdapterUSB2VGA {
public void projection() {
super.projection();
}
}
模式的应用实例
【例】用适配器模式(Adapter)模式新能源汽车的发动机。
分析:新能源汽车的发动机有电能发动机和光能发动机等,各种发动机的驱动,例如,电能发动机的驱动方法 electricDrive() 是用电能驱动,而光能发动机的驱动方法 opticalDrice() 用光能驱动,它们是适配器模式中被访问的适配器。
客户端希望用统一的发动机驱动方法 drive() 访问这两种发动机,所以必须定义一个统一的目标接口 Motor,然后再定义电能适配器(Electric Adapter)和光能适配器(Optical Adapter)去适配这两种发动机。结构图如下图所示。
程序代码如下:
// 目标:发动机
interface Motor {
public void drive();
}
// 适配者1:电能发动机
class ElectricMotor {
public void electricDrive()
{
System.out.println("电能发动机驱动汽车!");
}
}
// 适配者2:光能发动机
class OpticalMotor {
public void opticalDrive() {
System.out.println("光能发动机驱动汽车!");
}
}
// 电能适配器
class ElectricAdapter implements Motor {
private ElectricMotor emotor;
public ElectricAdapter() {
emotor = new ElectricMotor();
}
public void drive() {
emotor.electricDrive();
}
}
// 光能适配器
class OpticalAdapter implements Motor {
private OpticalMotor omotor;
public OpticalAdapter() {
omotor = new OpticalMotor();
}
public void drive() {
omotor.opticalDrive();
}
}
public class MotorAdapterTest {
public static void main(String[] args) {
Motor mEle = new ElectricAdapter();
mEle.drive();
Motor mOpt = new OpticalAdapter();
mOpt.drive();
}
}
程序运行结果:
电能发动机驱动汽车!
光能发动机驱动汽车!
应用场景
以前开发的系统存在满足新系统功能需求的类,但其接口同新系统的接口不一致。
使用第三方提供的组件,但组件接口定义和自己要求的接口定义不同。
类适配器模式:当希望将一个类转换成满足另一个新接口的类时,可以使用类的适配器模式,创建一个新类,继承原有的类,实现新的接口即可。
对象适配器模式:当希望将一个对象转换成满足另一个新接口的对象时,可以创建一个Wrapper类,持有原类的一个实例,在Wrapper类的方法中,调用实例的方法就行。
接口适配器模式:当不希望实现一个接口中所有的方法时,可以创建一个抽象类Wrapper,实现所有方法,我们写别的类的时候,继承抽象类即可。
桥接模式
在现实生活中,默写具有两个或多个维度的变化,如图像即可按形状分,又可按颜色分。如果设计类似于 Photoshop 这样的软件,如何去画不同形状和不同颜色的图像呢?如果用继承方式,m 种形状和 n 种颜色的图形就有 m×n 种,不但对应的子类很多,而且扩展困难。
当然,这样的例子还有很多,如不同颜色和字体的文字、不同品牌和功率的汽车、不同性别和职业的男女。。如果用桥接模式就能很好地解决这些问题。
定义和特点
桥接模式的定义如下:将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。
桥接模式的优点:
● 由于抽象与实现分离,所以扩展能力强。
● 其实现细节对客户透明。
缺点是:
● 由于聚合关系建立在抽象层,要求开发者针对抽象化进行设计与编程,这增加了系统的理解与设计难度。
结构与实现
桥接模式包含以下主要角色:
1)抽象化角色:定义抽象类,并包含一个对实现化对象的引用。
2)扩展抽象化角色:是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法。
3)实现化角色:定义是实现化角色的接口,供扩展抽象化角色调用。
4)具体实现化角色:给出实现化角色接口的具体实现。
其结构图如下图 所示。
应用实例
【例】用桥接(Bridge)模式模拟女士皮包的选购。
分析:女士皮包有很多种,可以按用途分,按皮质分、按品牌分、按颜色分、按大小分、存在多个维度的变化,所以采用桥接模式来实现女士皮包的选购比较合适。
本实例按用途分为:钱包(Wallet)和挎包(HandBag),按颜色分为:黄色(Yellow)和红色(Red)。可以按两个维度定义为颜色类和包类。
颜色类(Color)是一个维度,定义为实现化角色,它有两个具体实现化角色:黄色和红色,通过 getColor() 方法可以选择颜色。
包(Bag)是另一个维度,定义抽象化角色,它有两个扩展抽象化角色:钱包和挎包,它包含了颜色类对象,通过 getName() 方法可以选择相关颜色的钱包和挎包。
程序代码如下:
// 实现化角色:颜色
interface Color {
String getColor();
}
// 具体实现化角色:黄色
class Yellow implements Color {
public String getColor() {
return "yellow";
}
}
// 具体实现化角色:红色
class Red implements Color {
public String getColor() {
return "red";
}
}
// 抽象化角色:包
abstract class Bag {
protected Color color;
public void setColor(Color color) {
this.color = color;
}
public abstract String getName();
}
// 扩展抽象化角色:挎包
class HandBag extends Bag {
public String getName() {
return color.getColor() + "-HandBag";
}
}
// 扩展抽象化角色:钱包
class Wallet extends Bag {
public String getName() {
return color.getColor() + "-Wallet";
}
}
public class Brid
geTest {
public static void main(String[] args) {
Color yellow = new Yellow();
Bag bag = new Wallet();
bag.setColor(yellow);
System.out.println(bag.getName());
}
}
程序运行结果:
yellow-Wallet
应用场景
- 当一个类存在两个独立变化的维度,其这两个维度都需要进行扩展时。
- 当一个系统不希望使用继承或因为多层次继承导致系统类的个数急剧增加时。
- 当一个系统需要在结构的抽象画角色和具体化角色之间增加更多的灵活性时。
装饰模式
在现实生活中,常常需要对心有产品增加新的功能或美化其外观,如房子装修、相片加相框等。在软件开发过程中,有时想用一些现存的组件。这些组件可能只是完成了一些核心功能。但在不改变其结构的情况下,可以动态地扩展其功能。所有这些都可以釆用装饰模式来实现。
定义与特点
装饰模式的定义:指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式,它属于对象结构型模式。
装饰模式的主要优点有:
● 采用装饰模式扩展比采用继承方法更加灵活。
● 可以设计出多个不同的具体装饰类,创造出多个不同行为的组合。
其主要缺点是:
● 装饰模式增加了许多子类,如果过度使用会使程序变得很复杂。
结构与实现
(1)模式的结构
装饰模式主要包含以下角色。
1)抽象构件角色:定义一个抽象接口以规范准备接收附加责任的对象。
2)具体构件角色:实现抽象构件,通过装饰角色为其添加一些职责。
3)抽象装饰角色:继承抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
4)具体装饰角色:实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。
装饰模式的具体结构图如下图所示:
(2)模式的实现
装饰模式的实现代码如下:
public class DecoratorPattern {
public static void main(String[] args) {
Component p = new ConcreteComponent();
p.operation();
System.out.println("---------------------------------");
Component d = new ConcreteDecorator(p);
d.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();
addedFunction();
}
public void addedFunction() {
System.out.println("为具体构件角色增加额外的功能addedFunction()");
}
}
程序运行结果如下:
创建具体构件角色
调用具体构件角色的方法operation()
---------------------------------
调用具体构件角色的方法operation()
为具体构件角色增加额外的功能addedFunction()
应用实例
【例】用装饰模式实现游戏角色“莫莉卡·安斯兰”的变身。
分析:在《恶魔战士》中,游戏角色“莫莉卡·安斯兰”的原身是一个可爱少女,但当她变身时,会变成头顶及背部延伸出蝙蝠状飞翼的女妖,当然她还可以变为穿着漂亮外衣的少女。这些都可用装饰模式来实现,在本实例中的“莫莉卡”原身有 setImage(String t) 方法决定其显示方式,而其 变身“蝙蝠状女妖”和“着装少女”可以用 setChanger() 方法来改变其外观,原身与变身后的效果用 display() 方法来显示。
下图 所示其结构图:
程序代码如下:
//抽象构件角色:莫莉卡
interface Morrigan {
public void display();
}
//具体构件角色:原身
class original extends JFrame implements Morrigan {
private static final long serialVersionUID = 1L;
private String t="Morrigan0.jpg";
public original() {
super("《恶魔战士》中的莫莉卡·安斯兰");
}
public void setImage(String t) {
this.t = t;
}
public void display() {
this.setLayout(new FlowLayout());
JLabel l1=new JLabel(new ImageIcon("src/decorator/"+t));
this.add(l1);
this.pack();
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setVisible(true);
}
}
// 抽象装饰角色:变形
class Changer implements Morrigan {
Morrigan m;
public Changer(Morrigan m) {
this.m = m;
}
public void display() {
m.display();
}
}
// 具体装饰角色:女妖
class Succubus extends Changer {
public Succubus(Morrigan m) {
super(m);
}
public void display() {
setChanger();
super.display();
}
public void setChanger() {
((original) super.m).setImage("Morrigan1.jpg");
}
}
// 具体装饰角色:少女
class Girl extends Changer {
public Girl(Morrigan m) {
super(m);
}
public void display() {
setChanger();
super.display();
}
public void setChanger() {
((original) super.m).setImage("Morrigan2.jpg");
}
}
程序运行结果如下:
应用场景
- 当需要给一个现有类添加附加职责,而又不能采用生成子类的方法进行扩充时。例如,该类被隐藏或者该类是终极类或者采用继承方式会产生大量的子类。
- 当需要通过对现有的一组基本功能进行排列组合而产生非常多的功能时,采用继承关系很难实现,而采用装饰模式却很好实现。
- 当对象的功能要求可以动态地添加,也可以再动态地撤销时。
装饰模式在 Java 语言中的最著名的应用莫过于 Java I/O 标准库的设计了。
例如,InputStream
的子类 FilterInputStream
,OutputStream
的子类 FilterOutputStream
,Reader
的子类 BufferedReader
以及 FilterReader
,还有 Writer
的子类 BufferedWriter
、FilterWriter
以及 PrintWriter
等,它们都是抽象装饰类。
下面代码是为 FileReader 增加缓冲区而采用的装饰类 BufferedReader 的例子:
BufferedReader in = new BufferedReader(new FileReader("filename.txt"));
String str = in.readLine();
外观模式
在现实生活中,常常存在办事较复杂的例子,如办房产证或注册一家公司,有时要同多个部门联系,这时要是有一个综合部门能解决一切手续问题就好了。
软件设计也是这样,当一个系统的功能越来越强,子系统会越来越多,客户对系统的访问也变得越来越复杂。这时如果系统内部发生改变,客户端也要跟着改变,这违背了“开闭原则”,也违背了“迪米特法则”,所以有必要为多个子系统提供一个统一的接口,从而降低系统的耦合度,这就是外观模式的目标。
下图 给出了客户去当地房产局办理房产证过户要遇到的相关部门。
定义与特点
外观(Facade)模式的定义:是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口,外部应用程序不用关心内部子系统的具体的细节,这样会大大降低应用程序的复杂度,提高了程序的可维护性。
外观(Facade)模式是 “迪米特法则” 的典型应用,它有以下主要优点。
-
1)降低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户类。
-
2)对客户屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易。
-
3)降低了大型软件系统中的编译依赖性,简化了系统在不同平台之间的移植过程,因为编译一个子系统不会影响其他的子系统,也不会影响外观对象。
外观(Facade)模式的主要缺点如下。
-
1)不能很好地限制客户使用子系统类。
-
2)增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。
结构与实现
外观(Facade)模式的结构比较简单,主要是定义了一个高层接口。它包含了对各个子系统的引用,客户端可以通过它访问各个子系统的功能。现在来分析其基本结构和实现方法。
(1)模式的结构
外观(Facade)模式包含以下主要角色。
-
1)外观(Facade)角色:为多个子系统对外提供一个共同的接口。
-
2)子系统(Sub System)角色:实现系统的部分功能,客户可以通过外观角色访问它。
-
3)客户(Client)角色:通过一个外观角色访问各个子系统的功能。
其结构图如下图 所示。
(2)模式的实现
外观模式的实现代码如下:
// 外观角色
class Facade {
private SubSystem01 obj1 = new SubSystem01();
private SubSystem02 obj2 = new SubSystem02();
private SubSystem03 obj3 = new SubSystem03();
public void method() {
obj1.method1();
obj2.method2();
obj3.method3();
}
}
// 子系统角色
class SubSystem01 {
public void method1() {
System.out.println("子系统01的method1()被调用!");
}
}
//子系统角色
class SubSystem02 {
public void method2() {
System.out.println("子系统02的method2()被调用!");
}
}
// 子系统角色
class SubSystem03 {
public void method3() {
System.out.println("子系统03的method3()被调用!");
}
}
public class FacadePattern {
public static void main(String[] args) {
Facade f = new Facade();
f.method();
}
}
程序运行结果如下:
子系统01的method1()被调用!
子系统02的method2()被调用!
子系统03的method3()被调用!
应用实例
【例】用“外观模式”设计一个婺源特产的选购界面。
分析:本实例的外观角色 WySpecialty 是 JPanel 的子类,它拥有 8 个子系统角色 Specialty1~Specialty8,它们是图标类(ImageIcon)的子类对象,用来保存该婺源特产的图标。
外观类(WySpecialty)用 JTree 组件来管理婺源特产的名称,并定义一个事件处理方法 valueClianged(TreeSelectionEvent e),当用户从树中选择特产时,该特产的图标对象保存在标签(JLabd)对象中。
客户窗体对象用分割面板来实现,左边放外观角色的目录树,右边放显示所选特产图像的标签。
其结构图如下图 所示。
程序代码如下:
class WySpecialty extends JPanel implements TreeSelectionListener {
private static final long serialVersionUID=1L;
final JTree tree;
JLabel label;
private Specialty1 s1=new Specialty1();
private Specialty2 s2=new Specialty2();
private Specialty3 s3=new Specialty3();
private Specialty4 s4=new Specialty4();
private Specialty5 s5=new Specialty5();
private Specialty6 s6=new Specialty6();
private Specialty7 s7=new Specialty7();
private Specialty8 s8=new Specialty8();
WySpecialty() {
DefaultMutableTreeNode top = new DefaultMutableTreeNode("婺源特产");
DefaultMutableTreeNode node1 = null, node2 = null, tempNode = null;
// 四大特产
node1 = new DefaultMutableTreeNode("婺源四大特产(红、绿、黑、白)");
tempNode = new DefaultMutableTreeNode("婺源荷包红鲤鱼");
node1.add(tempNode);
tempNode=new DefaultMutableTreeNode("婺源绿茶");
node1.add(tempNode);
tempNode=new DefaultMutableTreeNode("婺源龙尾砚");
node1.add(tempNode);
tempNode=new DefaultMutableTreeNode("婺源江湾雪梨");
node1.add(tempNode);
top.add(node1);
// 其他特产
node2 = new DefaultMutableTreeNode("婺源其它土特产");
tempNode = new DefaultMutableTreeNode("婺源酒糟鱼");
node2.add(tempNode);
tempNode = new DefaultMutableTreeNode("婺源糟米子糕");
node2.add(tempNode);
tempNode = new DefaultMutableTreeNode("婺源清明果");
node2.add(tempNode);
tempNode = new DefaultMutableTreeNode("婺源油煎灯");
node2.add(tempNode);
top.add(node2);
// 树目录
tree = new JTree(top);
tree.addTreeSelectionListener(this);
label = new JLabel();
}
public void valueChanged(TreeSelectionEvent e) {
if(e.getSource() == tree) {
DefaultMutableTreeNode node=(DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
if(node==null) return;
if(node.isLeaf()) {
Object object = node.getUserObject();
String sele = object.toString();
label.setText(sele);
label.setHorizontalTextPosition(JLabel.CENTER);
label.setVerticalTextPosition(JLabel.BOTTOM);
sele = sele.substring(2,4);
if(sele.equalsIgnoreCase("荷包")) label.setIcon(s1);
else if(sele.equalsIgnoreCase("绿茶")) label.setIcon(s2);
else if(sele.equalsIgnoreCase("龙尾")) label.setIcon(s3);
else if(sele.equalsIgnoreCase("江湾")) label.setIcon(s4);
else if(sele.equalsIgnoreCase("酒糟")) label.setIcon(s5);
else if(sele.equalsIgnoreCase("糟米")) label.setIcon(s6);
else if(sele.equalsIgnoreCase("清明")) label.setIcon(s7);
else if(sele.equalsIgnoreCase("油煎")) label.setIcon(s8);
label.setHorizontalAlignment(JLabel.CENTER);
}
}
}
}
class Specialty1 extends ImageIcon {
private static final long serialVersionUID=1L;
Specialty1() {
super("src/facade/WyImage/Specialty11.jpg");
}
}
class Specialty2 extends ImageIcon {
private static final long serialVersionUID=1L;
Specialty2() {
super("src/facade/WyImage/Specialty12.jpg");
}
}
class Specialty3 extends ImageIcon {
private static final long serialVersionUID=1L;
Specialty3() {
super("src/facade/WyImage/Specialty13.jpg");
}
}
class Specialty4 extends ImageIcon {
private static final long serialVersionUID=1L;
Specialty4() {
super("src/facade/WyImage/Specialty14.jpg");
}
}
class Specialty5 extends ImageIcon {
private static final long serialVersionUID=1L;
Specialty5() {
super("src/facade/WyImage/Specialty21.jpg");
}
}
class Specialty6 extends ImageIcon {
private static final long serialVersionUID=1L;
Specialty6() {
super("src/facade/WyImage/Specialty22.jpg");
}
}
class Specialty7 extends ImageIcon {
private static final long serialVersionUID=1L;
Specialty7() {
super("src/facade/WyImage/Specialty23.jpg");
}
}
class Specialty8 extends ImageIcon {
private static final long serialVersionUID=1L;
Specialty8() {
super("src/facade/WyImage/Specialty24.jpg");
}
}
public class WySpecialtyFacade {
public static void main(String[] args) {
JFrame f = new JFrame ("外观模式: 婺源特产选择测试");
Container cp = f.getContentPane();
WySpecialty wys = new WySpecialty();
JScrollPane treeView = new JScrollPane(wys.tree);
JScrollPane scrollpane = new JScrollPane(wys.label);
// 分隔面板
JSplitPane splitpane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,true,treeView,scrollpane);
splitpane.setDividerLocation(230); // 设置splitpane的分隔线位置
splitpane.setOneTouchExpandable(true); // 设置splitpane可以展开或收起
cp.add(splitpane);
f.setSize(650,350);
f.setVisible(true);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
程序运行结果如下图 所示:
应用场景
-
1)对分层结构系统构建时,使用外观模式定义子系统中每层的入口点可以简化子系统之间的依赖关系。
-
2)当一个复杂系统的子系统很多时,外观模式可以为系统设计一个简单的接口供外界访问。
-
3)当客户端与多个子系统之间存在很大的联系时,引入外观模式可将它们分离,从而提高子系统的独立性和可移植性。
享元模式
在面向对象程序设计过程中,有时会面临要创建大量相同或相似对象实例的问题。创建那么多的对象将会耗费很多的系统资源,它是系统性能提高的一个瓶颈。
例如,围棋和五子棋中的黑白棋子,图像中的坐标点或颜色,局域网中的路由器、交换机和集线器,教室里的桌子和凳子等。这些对象有很多相似的地方,如果能把它们相同的部分提取出来共享,则能节省大量的系统资源,这就是享元模式的产生背景。
定义与特点
享元模式的定义:运用共享技术来有効地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似类的开销,从而提高系统资源的利用率。
享元模式的主要优点是:
- 相同对象只需要保存一份,这降低了系统中对象的数量,从而降低了系统中细粒度对象给内存带来的压力。
其主要缺点是:
-
1)为了使对象可以共享,需要将一些不能共享的状态外部化,这将增加程序的复杂性。
-
2)读取享元模式的外部状态会使得运行时间稍微变长。
结构与实现
享
元模式中存在以下两种状态:
-
内部状态,即不会随着环境的改变而改变的可共享部分;
-
外部状态,指随环境改变而改变的不可以共享的部分。享元模式的实现要领就是区分应用中的这两种状态,并将外部状态外部化。下面来分析其基本结构和实现方法。
(1)模式的结构
享元模式的主要角色有如下:
1)抽象享元(Flyweight)角色:是所有的具体享元类的基类,为具体享元规范需要实现的公共接口,非享元的外部状态以参数的形式通过方法传入。
2)具体享元(Concrete Flyweight)角色:实现抽象享元角色中所规定的接口。
3)非享元(Unsharable Flyweight)角色:是不可以共享的外部状态,它以参数的形式注入具体享元的相关方法中。
4)享元工厂(Flyweight Factory)角色:负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。
如 下图 所示,是享元模式的结构图,图中的 UnsharedConcreteFlyweight 是与淳元角色,里面包含了非共享的外部状态信息 info;
而 Flyweight 是抽象享元角色,里面包含了享元方法 operation(UnsharedConcreteFlyweight state),非享元的外部状态以参数的形式通过该方法传入;
ConcreteFlyweight 是具体享元角色,包含了关键字 key,它实现了抽象享元接口;
FlyweightFactory 是享元工厂角色,它逝关键字 key 来管理具体享元;
客户角色通过享元工厂获取具体享元,并访问具体享元的相关方法。
(2)模式的实现
享元模式的实现代码如下:
// 非享元角色
class UnsharedConcreteFlyweight {
private String info;
UnsharedConcreteFlyweight(String info) {
this.info=info;
}
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info=info;
}
}
// 抽象享元角色
interface Flyweight {
public void operation(UnsharedConcreteFlyweight state);
}
// 具体享元角色
class ConcreteFlyweight implements Flyweight {
private String key;
ConcreteFlyweight(String key) {
this.key = key;
System.out.println("具体享元" + key + "被创建!");
}
public void operation(UnsharedConcreteFlyweight outState) {
System.out.print("具体享元" + key + "被调用,");
System.out.println("非享元信息是:" + outState.getInfo());
}
}
// 享元工厂角色
class FlyweightFactory {
private HashMap<String, Flyweight> flyweights = new HashMap<String, Flyweight>();
public Flyweight getFlyweight(String key) {
Flyweight flyweight=(Flyweight)flyweights.get(key);
if(flyweight != null) {
System.out.println("具体享元"+key+"已经存在,被成功获取!");
} else {
flyweight=new ConcreteFlyweight(key);
flyweights.put(key, flyweight);
}
return flyweight;
}
}
public class FlyweightPattern {
public static void main(String[] args) {
FlyweightFactory factory=new FlyweightFactory();
Flyweight f01 = factory.getFlyweight("a");
Flyweight f02 = factory.getFlyweight("a");
Flyweight f03 = factory.getFlyweight("a");
Flyweight f11 = factory.getFlyweight("b");
Flyweight f12 = factory.getFlyweight("b");
f01.operation(new UnsharedConcreteFlyweight("第1次调用a。"));
f02.operation(new UnsharedConcreteFlyweight("第2次调用a。"));
f03.operation(new UnsharedConcreteFlyweight("第3次调用a。"));
f11.operation(new UnsharedConcreteFlyweight("第1次调用b。"));
f12.operation(new UnsharedConcreteFlyweight("第2次调用b。"));
}
}
程序运行结果如下:
具体享元a被创建!
具体享元a已经存在,被成功获取!
具体享元a已经存在,被成功获取!
具体享元b被创建!
具体享元b已经存在,被成功获取!
具体享元a被调用,非享元信息是:第1次调用a。
具体享元a被调用,非享元信息是:第2次调用a。
具体享元a被调用,非享元信息是:第3次调用a。
具体享元b被调用,非享元信息是:第1次调用b。
具体享元b被调用,非享元信息是:第2次调用b。
应用实例
【例】享元模式在五子棋游戏中的应用。
分析:五子棋同围棋一样,包含多个“黑”或“白”颜色的棋子,所以用享元模式比较好。本实例中的棋子(ChessPieces)类是抽象享元角色,它包含了一个落子的 DownPieces(Graphics g,Point pt) 方法;白子(WhitePieces)和黑子(BlackPieces)类是具体享元角色,它实现了落子方法;Point 是非享元角色,它指定了落子的位置;WeiqiFactory 是享元工厂角色,它通过 ArrayList 来管理棋子,并且提供了获取白子或者黑子的 getChessPieces(String type) 方法;客户类(Chessboard)利用 Graphics 组件在框架窗体中绘制一个棋盘,并实现 mouseClicked(MouseEvent e) 事件处理方法,该方法根据用户的选择从享元工厂中获取白子或者黑子并落在棋盘上。
下图 所示是其结构图。
程序代码如下:
// 棋盘
class Chessboard extends MouseAdapter {
WeiqiFactory wf;
JFrame frame;
Graphics g;
JRadioButton wz;
JRadioButton bz;
private final int x = 50;
private final int y = 50;
private final int w = 40; // 小方格宽度和高度
private final int rw = 400; // 棋盘宽度和高度
Chessboard() {
wf = new WeiqiFactory();
frame = new JFrame("享元模式在五子棋游戏中的应用");
frame.setBounds(100,100,500,550);
frame.setVisible(true);
frame.setResizable(false);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel SouthJP = new JPanel();
frame.add("South",SouthJP);
wz = new JRadioButton("白子");
bz = new JRadioButton("黑子", true);
ButtonGroup group = new ButtonGroup();
group.add(wz);
group.add(bz);
SouthJP.add(wz);
SouthJP.add(bz);
JPanel CenterJP = new JPanel();
CenterJP.setLayout(null);
CenterJP.setSize(500, 500);
CenterJP.addMouseListener(this);
frame.add("Center", CenterJP);
try {
Thread.sleep(500);
} catch(InterruptedException e) {
e.printStackTrace();
}
g = CenterJP.getGraphics();
g.setColor(Color.BLUE);
g.drawRect(x, y, rw, rw);
for(int i=1; i<10; i++) {
// 绘制第i条竖直线
g.drawLine(x+(i*w), y, x+(i*w), y+rw);
// 绘制第i条水平线
g.drawLine(x, y+(i*w), x+rw, y+(i*w));
}
}
public void mouseClicked(MouseEvent e) {
Point pt = new Point(e.getX() - 15, e.getY() - 15);
if(wz.isSelected()) {
ChessPieces c1=wf.getChessPieces("w");
c1.DownPieces(g,pt);
} else if(bz.isSelected()) {
ChessPieces c2=wf.getChessPieces("b");
c2.DownPieces(g,pt);
}
}
}
// 抽象享元角色:棋子
interface ChessPieces {
public void DownPieces(Graphics g, Point pt); //下子
}
// 具体享元角色:白子
class WhitePieces implements ChessPieces {
public void DownPieces(Graphics g, Point pt) {
g.setColor(Color.WHITE);
g.fillOval(pt.x,pt.y,30,30);
}
}
// 具体享元角色:黑子
class BlackPieces implements ChessPieces {
public void DownPieces(Graphics g, Point pt) {
g.setColor(Color.BLACK);
g.fillOval(pt.x, pt.y, 30, 30);
}
}
// 享元工厂角色
class WeiqiFactory {
private ArrayList<ChessPieces> qz;
public WeiqiFactory() {
qz = new ArrayList<ChessPieces>();
ChessPieces w = new WhitePieces();
qz.add(w);
ChessPieces b = new BlackPieces();
qz.add(b);
}
public ChessPieces getChessPieces(String type) {
if(type.equalsIgnoreCase("w")) {
return (ChessPieces)qz.get(0);
} else if(type.equalsIgnoreCase("b")) {
return (ChessPieces)qz.get(1);
} else {
return null;
}
}
}
public class WzqGame {
public static void main(String[] args) {
new Chessboard();
}
}
程序运行结果如下图所示:
应用场景
-
系统中存在大量相同或相似的对象,这些对象耗费大量的内存资源。
-
大部分的对象可以按照内部状态进行分组,且可将不同部分外部化,这样每一个组只需保存一个内部状态。
-
由于享元模式需要额外维护一个保存享元的数据结构,所以应当在有足够多的享元实例时才值得使用享元模式。
组合模式
在现实生活中,存在很多“部分-整体”的关系,例如,大学中的部门与学院、总公司中的部门与分公司、学习用品中的书与书包、生活用品中的衣月艮与衣柜以及厨房中的锅碗瓢盆等。
在软件开发中也是这样,例如,文件系统中的文件与文件夹、窗体程序中的简单控件与容器控件等。对这些简单对象与复合对象的处理,如果用组合模式来实现会很方便。
定义与特点
组合(Composite)模式的定义:有时又叫作部分-整体模式,它是一种将对象组合成树状的层次结构的模式,用来表示“部分-整体”的关系,使用户对单个对象和组合对象具有一致的访问性。
组合模式的主要优点有:
-
1)组合模式使得客户端代码可以一致地处理单个对象和组合对象,无须关心自己处理的是单个对象,还是组合对象,这简化了客户端代码;
-
2)更容易在组合体内加入新的对象,客户端不会因为加入了新的对象而更改源代码,满足“开闭原则”;
其主要缺点是:
-
1)设计较复杂,客户端需要花更多时间理清类之间的层次关系;
-
2)不容易限制容器中的构件;
-
3)不容易用继承的方法来增加构件的新功能;
结构与实现
组合模式的结构不是很复杂,下面对它的结构和实现进行分析。
(1)模式的结构
组合模式包含以下主要角色。
1)抽象构件(Component)角色:它的主要作用是为树叶构件和树枝构件声明公共接口,并实现它们的默认行为。在透明式的组合模式中抽象构件还声明访问和管理子类的接口;在安全式的组合模式中不声明访问和管理子类的接口,管理工作由树枝构件完成。
2)树叶构件(Leaf)角色:是组合中的叶节点对象,它没有子节点,用于实现抽象构件角色中 声明的公共接口。
3)树枝构件(Composite)角色:是组合中的分支节点对象,它有子节点。它实现了抽象构件角色中声明的接口,它的主要作用是存储和管理子部件,通常包含 Add()、Remove()、GetChild() 等方法。
组合模式分为透明式的组合模式和安全式的组合模式。
① 透明方式:在该方式中,由于抽象构件声明了所有子类中的全部方法,所以客户端无须区别树叶对象和树枝对象,对客户端来说是透明的。但其缺点是:树叶构件本来没有 Add()、Remove() 及 GetChild() 方法,却要实现它们(空实现或抛异常),这样会带来一些安全性问题。
其结构图如 下图 所示。
② 安全方式:在该方式中,将管理子构件的方法移到树枝构件中,抽象构件和树叶构件没有对子对象的管理方法,这样就避免了上一种方式的安全性问题,但由于叶子和分支有不同的接口,客户端在调用时要知道树叶对象和树枝对象的存在,所以失去了透明性。
其结构图如 下图 所示。
图 3-36 安全式4的组合模式的结构图
(2)模式的实现
假如要访问集合 c0 = {leaf1, {leaf2, leaf3}} 中的元素,其对应的树状图如 下图 所示。
下面给出透明式的组合模式的实现代码,与安全式的组合模式的实现代码类似,只要对其做简单修改就可以了。
// 抽象构件
interface Component {
public void add(Component c);
public void remove(Component c);
public Component getChild(int i);
public void operation();
}
/ / 树叶构件
class Leaf implements Component {
private String name;
public Leaf(String name) {
this.name=name;
}
public void add(Component c){}
public void remove(Component c){}
public Component getChild(int i) {
return null;
}
public void operation() {
System.out.println("树叶" + name + ":被访问!");
}
}
// 树枝构件
class Composite implements Component {
private ArrayList<Component> children = new ArrayList<Component>();
public void add(Component c) {
children.add(c);
}
public void remove(Component c) {
children.remove(c);
}
public Component getChild(int i) {
return children.get(i);
}
public void operation() {
for(Object obj:children) {
((Component)obj).operation();
}
}
public class CompositePattern {
public static void main(String[] args) {
Component c0 = new Composite();
// 添加树叶
Component leaf1 = new Leaf("1");
c0.add(leaf1);
// 添加树枝
Component c1 = new Composite();
c0.add(c1);
Component leaf2=new Leaf("2");
Component leaf3=new Leaf("3");
c1.add(leaf2);
c1.add(leaf3);
c0.operation();
}
}
程序运行结果如下:
树叶1:被访问!
树叶2:被访问!
树叶3:被访问!
应用实例
【例】用组合模式实现当用户在商店购物后,显示其所选商品信息,并计算所选商品总价的功能。
说明:假如李先生到韶关“天街e角”生活用品店购物:用 1 个红色小袋子装了 2 包婺源特产(单价 7.9 元)、1 张婺源地图(单价 9.9 元);
用 1 个白色小袋子装了 2 包韶关香藉(单价 68 元)和 3 包韶关红茶(单价 180 元);
用 1 个中袋子装了前面的红色小袋子和 1 个景德镇瓷器(单价 380 元);
用 1 个大袋子装了前面的中袋子、白色小袋子和 1 双李宁牌运动鞋(单价 198 元)。
最后“大袋子”中的内容有:
{
1 双李宁牌运动鞋(单价 198 元),
白色小袋子 {
2 包韶关香菇(单价 68 元),3 包韶关红茶(单价 180 元)
},
中袋子 {
1 个景德镇瓷器(单价 380 元),
红色小袋子 {
2 包婺源特产(单价 7.9 元),1 张婺源地图(单价 9.9 元)
}
}
},现在要求编程显示李先生放在大袋子中的所有商品信息并计算要支付的总价。
本实例可按安全组合模式设计,其结构图如 下图 所示。
程序代码如下:
// 抽象构件:物品
interface Articles {
public float calculation(); //计算
public void show();
}
// 树叶构件:商品
class Goods implements Articles {
private String name; // 名字
private int quantity; // 数量
private float unitPrice; // 单价
public Goods(String name, int quantity, float unitPrice) {
this.name = name;
this.quantity = quantity;
this.unitPrice = unitPrice;
}
public float calculation() {
return quantity * unitPrice;
}
public void show() {
System.out.println(name + "(数量:" + quantity + ",单价:" + unitPrice + "元)");
}
}
// 树枝构件:袋子
class Bags implements Articles {
private String name; // 名字
private ArrayList<Articles> arrBags = new ArrayList<>();
public Bags(String name) {
this.name = name;
}
public void add(Articles c) {
arrrBags.add(c);
}
public void remove(Articles c) {
arrBags.remove(c);
}
public Articles getChild(int i) {
return arrBags.get(i);
}
public float calculation() {
float s = 0;
for(Object obj : arrBags) {
s += ((Articles)obj).calculation();
}
return s;
}
public void show() {
for(Object obj : arrBags) {
((Articles)obj).show();
}
}
}
public class ShoppingTest {
public static void main(String[] args) {
Bags BigBag, mediumBag, smallRedBag, smallWhiteBag;
Goods sp;
smallRedBag = new Bags("红色小袋子");
sp = new Goods("婺源特产", 2, 7.9f);
smallRedBag.add(sp);
sp = new Goods("婺源地图", 1, 9.9f);
smallRedBag.add(sp);
smallWhiteBag = new Bags("白色小袋子");
sp = new Goods("韶关香菇", 2, 68);
smallWhiteBag.add(sp);
sp = new Goods("韶关红茶", 3, 180);
smallWhiteBag.add(sp);
mediumBag = new Bags("中袋子");
sp = new Goods("景德镇瓷器", 1, 380);
mediumBag.add(sp);
mediumBag.add(smallRedBag);
BigBag = new Bags("大袋子");
sp = new Goods("李宁牌运动鞋", 1, 198);
BigBag.add(sp);
BigBag.add(smallWhiteBag);
BigBag.add(mediumBag);
System.out.println("您选购的商品有:");
BigBag.show();
float s = BigBag.calculation();
System.out.println("要支付的总价是:" + s + "元");
}
}
程序运行结果如下:
您选购的商品有:
李宁牌运动鞋(数量:1,单价:198.0元)
韶关香菇(数量:2,单价:68.0元)
韶关红茶(数量:3,单价:180.0元)
景德镇瓷器(数量:1,单价:380.0元)
婺源特产(数量:2,单价:7.9元)
婺源地图(数量:1,单价:9.9元)
要支付的总价是:1279.7元
应用场景
-
1)在需要表示一个对象整体与部分的层次结构的场合。
-
2)要求对用户隐藏组合对象与单个对象的不同,用户可以用统一的接口使用组合结构中的所有对象的场合。