设计模式之建造者模式(七)
一、引出模式
开发场景:在前面工厂方法模式中我们提到出将数据以XML、TXT和数据库形式导出。现在我们再深化一下,对导出数据的格式进行约束。
导出的文件,不管是格式,都分为文件头、文件体和文件尾
文件头部分,需要描述:分公司名称、数据导出时间,
文件体部分,需要描述:表的名称,单独一行。还有一些数据描述。
文件尾部分,需要描述:导出人。
当我们不使用模式时,不管是输出输出成文本文件还是,输出到XML,步骤基本上是不变的,都要经过以下四步
1) 拼接文件头内容
2) 拼接文件体内容
3) 拼接文件尾内容
4) 将内容输出到相应格式
现在就是存在在使用输出格式时,都会重复这几个处理步骤,我们应该将其提炼出来,形成公共的处理过程,还要保证在处理过程不变的情况下,能方便的切换不同的输出格式。
二、认识模式
1.模式定义
将一个复杂对象的构建与它的表示相分离,使得同样的构建过程可以创建不同的表示。
在上述案例中,四步步骤就是构建过程,不同导出方式就是表示。
2.解决思路
要实现同样的构建过程可以创建不同的表现,第一件事就是将构建过程给独立出来,在建造者模式中把它称为指导者,由它来指导装配的过程,但不负责每步的具体实现。当然,光有指导者是不够的,必须要有能实现每步的对象,在建造者模式中称这些对象为建造器。
这样,指导者就是以重用的构建过程,而建造器是可以被切换的具体实现。
3.模式原型
Builder:建造器接口,定义创建一个Product对象所需要的各个部件的操作。
ConcreteBuilder:具体的建造器实现。实现各个部件的创建,并负责组将Product对象的各个部件,同时还提供一个让用户获取组将完成后的产品对象的方法。
Dorector:指导者,主要用来使用Builder接口,以一个统一程来构建所需要的Product对象。
Product:产品,表示被建造器构建的复杂对象,包含多个部件。
示例代码
class Program { static void Main(string[] args) { Director director = new Director(new ConcreteBuilder()); director.ConStruct(); Console.Read(); } } public class Director { //指导者需要持有建造器的引用 private Builder builders; //指定具体的建造器 public Director(Builder builder) { this.builders = builder; } //间接调用建造器的方法 public void ConStruct() { builders.BuildPart(); } } /// <summary> /// 建造器 /// </summary> public abstract class Builder { public abstract void BuildPart(); } /// <summary> /// 具体的建造器 /// </summary> public class ConcreteBuilder : Builder { //建造器需要持有产品的引用 private Product resultProduct; public override void BuildPart() { Console.WriteLine("BuilderPartA"); } /// <summary> /// 返回产品 /// </summary> /// <returns></returns> public Product GetResult() { return resultProduct; } } /// <summary> /// 产品 /// </summary> public class Product { } |
4.上述案例的模式结构
示例代码:
class Program { static void Main(string[] args) { //准备测试数据 ExportHeaderModel ehm = new ExportHeaderModel(); ehm.DepId = "一分公司"; ehm.ExportDate = "2010-05-18"; Dictionary<String, List<ExportDataModel>> dicData = new Dictionary<String,
List<ExportDataModel>>();
List<ExportDataModel> list = new List<ExportDataModel>();
ExportDataModel edm1 = new ExportDataModel();
edm1.ProductId = "产品001号";
edm1.Price = 100;
edm1.Amount = 80;
ExportDataModel edm2 = new ExportDataModel();
edm2.ProductId = "产品002号";
edm2.Price = 99;
edm2.Amount = 55;
//把数据组装起来
list.Add(edm1);
list.Add(edm2);
dicData.Add("销售记录表", list);
ExportFooterModel efm = new ExportFooterModel();
efm.ExportUser = "张三";
//测试输出到文本文件
TxtBuilder txtBuilder = new TxtBuilder();
//创建指导者对象
Director director = new Director(txtBuilder);
director.Construct(ehm, dicData, efm);
Console.WriteLine("输出到文本文件的内容:\n" +
txtBuilder.GetResult());
Console.WriteLine("--------------------------------------------------------");
//测试输出到xml文件
XmlBuilder xmlBuilder = new XmlBuilder();
Director director2 = new Director(xmlBuilder);
director2.Construct(ehm, dicData, efm);
//xmlBuilder.BuildHeader(ehm);
//xmlBuilder.BuildBody(dicData);
//xmlBuilder.BuildFooter(efm);
//把要输出的内容输出到控制台看看
Console.WriteLine("输出到XML文件的内容:\n" +
xmlBuilder.GetResult());
Console.Read();
}
}
#region 指导者
/// <summary>
/// 指导者,指导使用构建器的接口来构建输出的文件的对象
/// </summary>
public class Director
{
/// <summary>
/// 持有当前需要使用的构建器对象
/// </summary>
private Builder builder;
/// <summary>
/// 构造方法,传入构建器对象
/// </summary>
/// <param name="builder"></param>
public Director(Builder builder)
{
this.builder = builder;
}
/// <summary>
/// 指导构建器构建最终的输出的文件的对象
/// </summary>
/// <param name="ehm">文件头的内容</param>
/// <param name="dicData">数据的内容</param>
/// <param name="efm">文件尾的内容</param>
public void Construct(ExportHeaderModel ehm, Dictionary<String,
List<ExportDataModel>>
dicData,
ExportFooterModel efm)
{
//可以在这进行校验
//1:先构建Header
builder.BuildHeader(ehm);
//实现一个Header的后处理
//可以在这里实现一些业务
//2:然后构建Body
builder.BuildBody(dicData);
//3:然后构建Footer
builder.BuildFooter(efm);
}
}
#endregion
#region Builder接口
/// <summary>
/// 构建器接口,定义创建一个输出文件对象所需的各个部件的操作
/// </summary>
public interface Builder
{
/// <summary>
/// 构建输出文件的Header部分
/// </summary>
/// <param name="ehm"></param>
void BuildHeader(ExportHeaderModel ehm);
/// <summary>
/// 构建输出文件的Body部分
/// </summary>
/// <param name="mapData"></param>
void BuildBody(Dictionary<string, List<ExportDataModel>>
dicData);
/// <summary>
/// 构建输出文件的Footer部分
/// </summary>
/// <param name="efm"></param>
void BuildFooter(ExportFooterModel efm);
}
#endregion
#region ConcreteBuilder 导出到Txt
/// <summary>
/// 实现导出数据到文本文件的的构建器对象
/// </summary>
public class TxtBuilder : Builder
{
/// <summary>
/// 用来记录构建的文件的内容,相当于产品
/// </summary>
private StringBuilder strBuilder = new StringBuilder();
public void BuildBody(Dictionary<string, List<ExportDataModel>>
dicData)
{
foreach (var tblName in dicData.Keys)
{
strBuilder.Append(tblName + "\n");
foreach (ExportDataModel edm in dicData.Values.FirstOrDefault())
{
strBuilder.Append(edm.ProductId + ","
+ edm.Price + "," + edm.Amount);
}
}
}
public void BuildFooter(ExportFooterModel efm)
{
strBuilder.Append(efm.ExportUser);
}
public void BuildHeader(ExportHeaderModel ehm)
{
strBuilder.Append(ehm.DepId + "," +
ehm.ExportDate + "\n");
}
public string GetResult()
{
return strBuilder.ToString();
}
}
#endregion
#region ConcreteBuilder 导出到XML文件
/// <summary>
/// 实现导出数据到XML文件的的构建器对象
/// </summary>
public class XmlBuilder : Builder
{
/// <summary>
/// 用来记录构建的文件的内容,相当于产品
/// </summary>
private StringBuilder strBuilder = new StringBuilder();
public void BuildHeader(ExportHeaderModel ehm)
{
strBuilder.Append("<?xml version='1.0'
encoding='gb2312'?>\n");
strBuilder.Append("<Report>\n");
strBuilder.Append(" <Header>\n");
strBuilder.Append(" <DepId>" +
ehm.DepId + "</DepId>\n");
strBuilder.Append(" <ExportDate>"
+ ehm.ExportDate + "</ExportDate>\n");
strBuilder.Append(" </Header>\n");
}
public void BuildBody(Dictionary<string, List<ExportDataModel>>
dicData)
{
strBuilder.Append(" <Body>\n");
foreach (var tblName in dicData.Keys)
{
strBuilder.Append(" <Datas TableName=\""
+ tblName + "\">\n");
foreach (ExportDataModel edm in dicData.Values.FirstOrDefault())
{
strBuilder.Append(" <Data>\n");
strBuilder.Append(" <ProductId>"
+ edm.ProductId +
"</ProductId>\n");
strBuilder.Append(" <Price>" +
edm.Price + "</Price>\n");
strBuilder.Append(" <Amount>"
+ edm.Amount + "</Amount>\n");
strBuilder.Append(" </Data>\n");
}
strBuilder.Append(" </Datas>\n");
}
strBuilder.Append(" </Body>\n");
}
public void BuildFooter(ExportFooterModel efm)
{
//对象的创建过程
//不是由自己来创建对象,而是使用其它组件创建的对象
//比如:简单工厂、工厂方法
MyFooter mf = FooterFactory.createMyFooter();
//组件组装过程
strBuilder.Append(mf.GenHeader(efm));
}
public string GetResult()
{
return strBuilder.ToString();
}
}
/// <summary>
/// XML 文件尾工厂
/// </summary>
public class FooterFactory
{
public static MyFooter createMyFooter()
{
return new MyFooter();
}
}
/// <summary>
/// XML 具体实现
/// </summary>
public class MyFooter
{
public String GenHeader(ExportFooterModel efm)
{
String str = " <Footer>\n";
str += " <ExportUser>" + efm.ExportUser
+ "</ExportUser>\n";
str += " </Footer>\n";
str += "</Report>\n";
return str;
}
}
#endregion
#region 产品
/// <summary>
/// 文件头部分
/// </summary>
public class ExportHeaderModel
{
/// <summary>
/// 分公司或门市点编号
/// </summary>
public String DepId { get; set; }
/// <summary>
/// 导出数据的日期
/// </summary>
public String ExportDate { get; set; }
}
/// <summary>
/// 文件体部分
/// </summary>
public class ExportDataModel
{
/// <summary>
/// 产品编号
/// </summary>
public string ProductId { get; set; }
/// <summary>
/// 产品价格
/// </summary>
public int Price { get; set; }
/// <summary>
/// 产品数量
/// </summary>
public int Amount { get; set; }
}
/// <summary>
/// 文件尾部分
/// </summary>
public class ExportFooterModel
{
/// <summary>
/// 输出人
/// </summary>
public String ExportUser { get; set; }
}
#endregion |
三、理解模式
1.认识模式
按照封装变化的原理,建造者模式实则是封装对象创建的变化,主要是指对象内部构建的创建。建造者模式的主要功能就是构建复杂的产品,这个构建的过程是统一的,固定不变的,变化的部分放到建造器部分,只要配置不同的建造器,同样的构建过程,就能出来不同的产品。
建造者模式的重心在于分离构建算法和具体的构造实现。
2.建造者模式的构成
建造者模式分为两个很重要的部分。
1) 一个是Builder接口,这里定义了如何构建各个部件,也就是知道每个部件功能如何实现。
2) 另一部分就是Director,指导者是知道如何组合来构建产品,也就是负责整体的算法。
不论怎么变化,建造者模式都是存在这两个部分的,一部分是部件构造和产品装配,另一部分是整体构建的算法。注意,这里是说部分,而不是一定要存在指导者或者Builder接口这两个类。
3.关于被构建的产品的接口
在使用建造者模式时,大多数情况下是不知道最终构建出来的产品是怎么样的,所以在标准的建造者模式中,一般是不需要给产品定义接口的。
4.建造者模式的演化
建造者模式在使用的过程中可以演化出多种形式。
1)省略抽象建造者角色
如果系统中只需要一个具体建造者的话,可以省略掉抽象建造者。
2)省略指导者角色
在具体建造者只有一个的情况下,如果抽象建造者角色已经被省略掉,那么还可以省略掉指导者角色。让Builder角色自己扮演指导者与建造者双重角色。
只要牢牢把握,建造者模式的两个主要部分(第2点提到)就可以了。
5.建造者模式的优点
1) 松散耦合
2) 可以很容易地改变产品内部表示
3) 更好的复用性
6.何时选用建造者模式
1).如果创建对象的算法,应该独立于该对象的组成不封、 以及它们的装配方式时,使用建造者模式。
2).如果同一个构建过程有着不同的表示时,使用建造者模式。
7.相关模式
建造者模式和工产方法模式
这两个模式可以组合使用。
工厂方法模式与建造者模式并没有泾渭分明的区别,尤其针对建造者模式,可以将其转化为工厂方法模式,反之则不然。也就是说工厂方法模式应用更加广泛。如果一定要做区分,二者都是生产产品的,工厂方法模式主要用于生成一类完整的产品,而建造者模式则更关注对产品内部的创建进行组合,并最终组装为整体的产品。
建造者模式与抽象工厂
这两个模式可以组合使用。
在建造者模式实现中,需要创建各个部件对象,而这些部件对象是有关联的,通常是构成一个复杂对象的部件对象。也就是说,建造者模式实现中,需要获取构成一个复杂对象的产品簇,那么自然就可以使用抽象工厂模式来实现了,这样,抽象工厂负责部件对象的创建,建造者模式实现里则主要负责产品对象的整体构建。
8.小结
建造者模式重心还在于分离整体构建算法和部件构造。分步骤构建对象不过是整体构建算法的一个简单表现,或者说是一个附带的产物。
设计模式之门面模式(八)
本篇学习的是门面模式,又称外观模式,个人感觉还是成为门面模式比较形象,是一种结构型模式。
一、引出模式
在生活中,比如我们准备自己配一台电脑,一种解决方案就是自己去电子市场一家一家的去询问,去购买,无疑这样是很浪费时间和精力的,另外一种解决方案就是直接去找一家装机公司,你把需求告诉它,它自然就会把相应的机器组装给你,这样你就只要知道装机公司就行,省了中间许多环节。这时的装机公司,我们可将其称为门面(Facade).
那我们将上面生活例子抽象一下,将电子市场看作一个系统,我们看作客户端。客户端为了完成某项功能,需要使用到系统中的许多模块,比如A模块,B模块和C模块。对于客户端来说,他就必须知道A、B、C三个模块的功能,还要知道应该如何组织这三种模块才能完成功能。这样就很大程度的违反了最少知识原则。如果现在我们提供了一种功能,将系统中的A、B、C三个模块的功能进行封装,客户端直接调用这个封装的功能,客户端就不用那么麻烦了。这个封装的功能就是Fa?ade。
二、认识模式
1.模式定义
为子系统中的一组接口提供一个一致的界面,Fa?ade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
在这里“界面”和“接口”要解释一下。
界面:这里的"界面"是指从一个组件外部来看这个组件,比如从一个类的外部看这个类,我们只能看到Public属性或方法,同理,我们从一个组件外部来看这个组件,我们只能看到这个组件公开的接口,而看不到该组件的内部实现。
接口:这里的接口是指组件外部与内部交互的一个通道,一般是一些方法。
用大白话来讲就是:为子系统中的一些方法提取到一个类中并将其封装,客户端只需要使用这个封装的类中的方法就可以间接使用子系统中的方法了。这个类就是Fa?ade类。
2.解决思路
门面模式就是通过引用一个门面类,在这个类中定义客户端需要的一些简单方法,然后在这些个方法中在通过该门面类去调用内部的多个模型进行实现所需功能,这样客户端只需与门面类交互即可。
3.模式结构
1) Fa?ade
定义子系统的多个模块对外的高层 接口,通常需要调用内部多个模块,从而将请求转调给适合的子系统对象。
2) 模块
接受Fa?ade对象的委派,真正实现功能,各模块直接可能相互协作。
Fa?ade对象是需要知道各个模块的,但是各个模块是不必知道Fa?ade对象的。
4.示例代码
class Program { static void Main(string[] args) { Facade facade = new Facade(); facade.Test(); Console.ReadKey(); } } public class Facade { public void Test() { IAModuleApi aapi=new AModuleApi(); aapi.TestA(); IBModuleApi bapi=new BModuleApi(); bapi.TestB(); ICModuleApi capi = new CModuleApi(); capi.TestC(); } } public interface IAModuleApi { void TestA(); } public class AModuleApi:IAModuleApi { public void TestA() { Console.WriteLine("使用了AModuleApi"); } } public interface IBModuleApi { void TestB(); } public class BModuleApi : IBModuleApi { public void TestB() { Console.WriteLine("使用了BModuleApi"); } } public interface ICModuleApi { void TestC(); } public class CModuleApi : ICModuleApi { public void TestC() { Console.WriteLine("使用了CModuleApi"); } } |
三、理解模式
1.门面模式的目的
门面模式的目的不是给子系统添加一个新的功能接口,而是为了减少外部与子系统内多个模块进行交互,松散耦合,从而让外部能够更简单的使用子系统。
需要再次强调的是,门面模式是负责组合子系统已有的功能来实现外部的需要,而不是添加新的实现。
2.使用门面和不使用门面到底差多少
很多人觉得,门面模式只不过是将原来客户端的代码封装到了Fa?ade里面了,也没多大区别嘛?
前面一句说的是非常对的,当时使用的门面模式后,它们的本质发生了颠覆性的变化。
第一点,它相当于一扇窗口,屏蔽了外部客户端和系统内部模块的交互,从而是A、B、C模块组合成了一个整体,至此,客户端不必知道我要先找睡,再找随,你只需要知道找这个窗口就能办理手续了。这样,不但方便客户端的调用,而且封装了系统内部的细节功能。
第二点,Fa?ade的功能可以被很多客户端调用。
3.门面模式提供了缺省的功能实现。
随着系统越来越大,Fa?ade中的方法也会越来越多,我们可以在Fa?ade类中封装外部最常用的,最复杂的功能,至于其他功能,让客户端绕过Fa?ade直接对内部子系统进行调用。
4.Fa?ade实现
1)对于一个子系统来说,Fa?ade类,无需太多,我们可以将其实现为单例,也可以将它定义成一个静态类,当作工具类来使用。
2)当然我们还可以将Fa?ade实现成为一个Interface接口,这样会增加系统的复杂程度,因为你需要一个工厂来多去Fa?ade的接口对象。
这样做的好处就是能够有选择性地暴露接口的方法,尽量减少模块对子系统外提供的接口方法。
5.模式的优点
1) 松散耦合
2) 简单易用
3) 更好的的划分访问的层次
6.何时使用门面模式
1) 为一个复杂子系统提供一个简单接口
2) 提高子系统的独立性
3) 在层次化结构中,可以使用Facade模式定义系统中每一层的入口。
7.模式小结
门面模式的本质就是:封装交互,简化调用。
Facade封装了子系统外部与子系统内部多个模块的交互过程,从而简化了外部的调用。
设计模式之适配器模式(九)
一、引出模式
1.生活中的例子
大家都是知道,天朝家用电器的标准电压和美国标准电压是不同的,也就是我们把在天朝买的笔记本带到美国去也无法正常使用的,想要使用就必须跟换电源适配器。将电源适配器的电压换成适应美国的电压就可以正常使用的。而这个电源适配器的功能就是本篇所要学习的主题“适配器(Adapter)”。
2.软件实例
比如现在要做一个日志记录的系统,在第一版中使用文件为载体,可以对日志文件进行新增和删除,随着业务的扩大,文件日志已无法满足需求,于是在第二版中采用了数据库来存储日志,并能对日志进行CRUD。由于客户的多变,现在告诉你,这两种功能都要。
问题出现了,就是第二版中定义的接口,对第一版并不是适用,也就是说,现在的客户端无法以同样的方式直接使用第一版。想要客户端同时使用这两种,就必须额外的做些工作。
有这么几种解决方案:1.在第二版中重新在写一个第一版的功能,这就是相当于白做了,明明已经有现成的了,还要再重写。2.修改第一版,迫使第一版和第二版达成兼容共识,但是第一版不是你开发的或者第一版依赖了很多项目,那怎么办?
二、认识模式
1.适配器模式定义
将一个已存在类的接口转化成客户希望的另一个接口。适配器模式使得原本由于接口不兼容问题而不能一起工作的那些类可以一起工作。
注意定义中的几个关键字“已存在类”,“转化”.
2.解决方案
该问题的根源就是新老系统接口不兼容,具体的功能都已实现,现在要做的就是是这两个接口匹配起来。
我们可以定义一个类来实现新系统的接口,然后在内部实现时转调老系统已经实现的功能,这样通过对象组合的方式,既复用了老系统的已有的功能,又在接口上满足新系统调用的要求。
3.模式原型
Clint:客户端,调用自己需要的领域接口Itarget。
ITarget:定义客户端需要的与相关领域特定的接口。
Adaptee:已存在的接口,通常能满足客户端的功能需求,但是接口与客户端要求的特定领域接口不一致,需要被适配。
Adapter:适配器,把Adaptee适配成为Client需要的ITarget。
4.模式原型代码示例
class Program { static void Main(string[] args) { //创建需要被适配的对象 Adaptee adaptee = new Adaptee(); //创建客户端需要调用的接口对象 ITarget traget = new Adapter(adaptee); //请求处理 traget.Request(); Console.ReadKey(); } } public interface ITarget { void Request(); } /// <summary> /// 已存在的接口,需要被适配 /// </summary> public class Adaptee { public void SpecificRequest() { Console.WriteLine("调用老系统功能"); } } /// <summary> /// 适配器 /// </summary> public class Adapter : ITarget { /// <summary> /// 持有需要被适配的接口对象 /// </summary> Adaptee adaptee = null; /// <summary> /// 构造函数,传入需要被适配的对象 /// </summary> /// <param name="adaptee"></param> public Adapter(Adaptee adaptee) { this.adaptee = adaptee; } public void Request() { //转调 adaptee.SpecificRequest(); } } |
三、理解模式
1.模式功能:
适配器模式主要功能是进行转化匹配,目的是复用现有的功能,而不是实现新的接口。也就是说,客户端需要的功能是已经实现好了的,不需要适配器模式来实现,适配器模式主要负责把不兼容的接口转化成客户端期望的样子就行了。
这并不是说,在适配器里面不能实现功能。适配器里面可以实现功能,这种适配器叫做“智能适配器”,再说了,在适配的过程中,也有一些需要特定的功能,才能转换过来,比如调整参数以进行适配。
2.Adaptee和ITarget的关系
适配器模式中被适配的接口Adaptee和适配成为的接口ITarget是没有关联的,也就是说,Adaptee和ITarget中的方法既可以相同,也可以不同。
3.对像组合
适配器的实现方式其实是依靠对象组合的方式。通过给适配器对象组合被被适配的对象,让后当客户端调用ITarget时,适配器会把相应的功能委托给被适配的对象去完成。
4.适配器的常见实现
在实现适配器实现时,适配器通常是一个类,一般会让适配器类去实现ITarget接口,让后在适配器的具体实现里面调用Adaptee。也就是说适配器通常是一个ITarget类型,而不是Adaptee类型。
5.智能适配器
在开发中,适配器也可以实现一些Adaptee没有的功能,但在ITarget定义了这些功能。这种情况就需要在适配器的实现里面,加入新功能的实现。这种适配器被成为智能适配器。
6.适配多个Adaptee
适配器在适配时,可以适配多个Adaptee,也就是说实现某个新的ITarget的功能时,需要调用多个模块的功能,这样才能满足新接口的要求。
7.缺省适配
缺省适配,就是为一个接口提供缺省实现。有了它,就可以不用直接去实现接口,而是采用继承这个缺省适配对象。
8.双向适配器:
先前都是把Adaptee适配成为ITarget,其实也可以将ITarget适配成为Adaptee。也就是说这个适配器类可以作为ITarget用也可以作为Adaptee用。
使用适配器有一个潜在问题,就是被适配的对象不再兼容Adaptee的接口,应为适配器只是实现了ITarget接口。这导致并不是所有的Adaptee对象可以使用的地方都能使用适配器。
双向适配器就解决了这样的问题,双向适配器同时实现了ITarget和Adaptee的接口,使得双向适配器可以在ITarget或Adaptee被使用的地方使用,以提供对说有客户的透明性。
9.对象适配器
对象适配器的实现依赖于对象组合,就像前面示例的。
10.类适配器
类适配器的实现采用多重继承对一个接口与另一个接口进行匹配。
11.对象适配器和类适配器的权衡
类适配器使用对象继承,是静态定义方式;对象适配器使用对象组合,是动态组合方式。
12.适配器模式的优点
1) 更好的复用性
2) 更好的可扩展性
13.适配器模型的缺点
过多地使用适配器,会让系统非常零乱,不容易对整体的把握。
14.何时选用适配器模式
1) 系统需要使用现有的类,而此类的接口不符合系统的需要。
2) 想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。这些源类不一定有很复杂的接口。
3) (对对象适配器而言)在设计里,需要改变多个已有子类的接口,如果使用类的适配器模式,就要针对每一个子类做一个适配器,而这不太实际。
15.小结
适配器模式的本质是:转化匹配,复用功能。
适配器通过转调已有的实现,从而能把已有的实现匹配成为需要的接口,使之能满足客户端的需要。也就是说转换匹配是手段,而复用功能才是目的。 |