简介
今天与大家分享得是大话设计模式中的结构型模式,结构型模式包括(适配器、桥接、组合、装饰、外观、享元、代理)七大模式。其中所涉及到的问题,扩展性(外观、代理、装饰、组合),封装性(适配器、桥接)
什么是结构型模式
结构型模式涉及到如何组合类和对象以获得更大的结构即是通过多个类和对象来实现更复杂的结构。
结构型模式思维导图
适配器模式
定义:将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
个人理解,系统的数据合行为都正确,但接口不符时,我们应该考虑用适配器,目的时使控制范围之外的一个原有对象与某个接口匹配。也就是说,需要的东西就在面前,但不能使用,短时间内又无法改造它,就只能去适配它。
其中,适配器模式讲了两种类型,即类适配器模式合对象适配器模式,这里主要讲对象适配器模式。
用法:适配器模式主要用于希望复用一些现存的类,但是接口又与复用环境要求不一致的情况。在双方都不太容易修改的时候再使用适配器模式适配。一般在软件开发的后期或维护期使用,也有在设计之初就使用的,如公司设计一系统,所需要的第三方组件,不必为了迎合这一接口而去改动自己的接口。
.NET应用
DataAdapter的使用,它是用作DataSet和数据源以便检索和保存数据。(目前没用过,没多大感触)
结构图
客户所期待的接口。
class Target
{
public virtual void Request()
{
Console.WriteLine("普通请求!");
}
}
Adaptee(需要适配的类)
class Adaptee
{
public void SpecificRequest()
{
Console.WriteLine("特殊请求");
}
}
Adapter(通过在内部包装一个Adaptee对象,把源接口转换成目标接口)
class Adapter : Target
{
private Adaptee adaptee = new Adaptee(); //建立一个私有的Adaptee对象
public override void Request()
{
adaptee.SpecificRequest();
}
}
客户端
Target target = new Adapter();
target.Request();
举例姚明不懂英语美国打球
客户所期待看到的接口,Player类
abstract class Player
{
protected string name;
public Player(string name)
{
this.name = name;
}
public abstract void Attack();
public abstract void Defense();
}
外籍中锋姚明类
class ForeignCenter
{
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
public void 进攻()
{
Console.WriteLine("外籍中锋{0}进攻",name);
}
public void 防守()
{
Console.WriteLine("外籍中锋{0}防守", name);
}
}
翻译者类
class Translator : Player
{
private ForeignCenter wjzf = new ForeignCenter();
public Translator(string name) : base(name)
{
wjzf.Name = name;
}
public override void Attack()
{
wjzf.进攻();
}
public override void Defense()
{
wjzf.防守();
}
}
客户端
Player b = new Forwards("巴蒂尔");
b.Attack();
Player ym = new Translator("姚明");
ym.Attack();
ym.Defense();
这里有一个abstract 和virtual的区别,virtual有主体,abstrct可以没有主体
桥接模式
定义:将抽象部分与它的实现部分分离,使它们都可以独立地变化。
个人理解,相当于书上的例子,“手机”可以按照品牌分类,也可以按照功能分类,并不是说让抽象类与其派生类分离。
原则:符合开放-封闭原则和合成-聚合复用原则,特别是合成-聚合原则,解决盲目使用继承而造成的不必要麻烦。继承是一种强耦合的结构,父类变,子类就必须变;所以我们在使用继承时,一定要是"is-a"的关系时再考虑使用,而不是任何时候都去使用。
为什么有桥接模式:合成聚合复用原则,是尽量使用合成/聚合,尽量不要使用类继承。聚合表示一种强有的关系,如大雁和雁群就是聚合关系。合成则是一种强有的拥有关系,体现了严格的整体-部分关系。如大雁和翅膀是整体与部分的关系。此原则的好处就是可以优先使用对象的合成/聚合将有助于你报错每个类被封装,并被集中在单个任务上。这样类和类继承层次会保持较小的规模,就不会因为使用继承使用不当而造成不可控制的庞然大物。从而引出了桥接模式。
结构图:
下面结合书中手机品牌的例子来标记桥接模式的基本代码
手机软件抽象类
abstract class Implementor
{
public abstract void Operation();
}
软件里的具体功能实现
class ConcreteImplementorA : Implementor
{
public override void Operation()
{
Console.WriteLine("具体实现A的方法执行");
}
}
手机品牌抽象类
class Abstraction
{
protected Implementor implementor;
public void SetImplementor(Implementor implementor)
{
this.implementor = implementor;
}
public virtual void Operation()
{
implementor.Operation();
}
}
品牌N或M具体的类
class RefinedAbstraction : Abstraction
{
public override void Operation()
{
implementor.Operation();
}
}
客户单
Abstraction ab = new RefinedAbstraction();
ab.SetImplementor(new ConcreteImplementorA());
ab.Operation();
ab.SetImplementor(new ConcreteImplementorB());
ab.Operation();
组合模式
定义:将对象组合成树形结构以表示‘部分-整体’的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
个人理解,也就是达到复用的效果,且具有一致性。如对一个文字的处理和对多个文字的处理效果都一样。
何时使用:当发现需求中是体现部分与整体层次的结构时,及希望用户可以忽略组合对象与单个对象的不同,统一地使用组合结构总得所有对象时,就可以考虑使用它了。
好处:使用了此模式,华为手机公司无论开多少个以及多少级办事处以及开在全世界或者是全国各地,客户们多得到的服务体验都是一样的。
拓展:此模式涉及到了透明方式和安全方式,透明方式使其所使用的的方法具备完全一致的行为接口,虽然没有意义。就比如书中例子中叶本身没有叶了,但为了使其具备一致的行为,又把此增加了叶。安全模式不用做这样无意义的工作,但需判断他们之间的接口是否具有一致的行为,这又增加了麻烦。
结构图
下面以总公司到分公司的例子带入代码
总公司抽象类
abstract class Component
{
protected string name;
public Component(string name)
{
this.name = name;
}
public abstract void Add(Component c);
public abstract void Remove(Component c);
public abstract void Display(int depth);
}
具体公司类,实现接口 树枝节点
class Composite : Component
{
private List<Component> childeren = new List<Component>();//存储下属的枝节点和叶节点
public Composite(string name):base(name) { }
public override void Add(Component c)
{
childeren.Add(c);
}
public override void Remove(Component c)
{
childeren.Remove(c);
}
public override void Display(int depth)
{
Console.WriteLine(new string ('-',depth)+name); //显示其枝节点名称,并对其下级进行遍历
foreach (Component component in childeren)
{
component.Display(depth + 2);
}
}
}
公司下面所属的部门类
class Leaf : Component
{
public Leaf (string name):base(name)
{
}
public override void Add(Component c) //叶子没有再增加分枝和树叶,但这样做可以消除叶节点和枝节点对象再抽象层次的区别,使其具备完全一致的接口
{
Console.WriteLine("Cannot add to a leaf");
}
public override void Remove(Component c)
{
Console.WriteLine("Cannot remove to a leaf");
}
public override void Display(int depth)
{
Console.WriteLine(new string('-', depth) + name);//显示名称和级别
}
}
客户端
Composite root = new Composite("root"); //总公司
root.Add(new Leaf("Leaf A"));//部门
root.Add(new Leaf("Leaf B"));
Composite comp = new Composite("Composite X");//分公司
comp.Add(new Leaf("Leaf XA"));
comp.Add(new Leaf("Leaf XB"));
root.Add(comp);
Composite comp2 = new Composite("Composite XY");
comp2.Add(new Leaf("Leaf XYA"));
comp2.Add(new Leaf("Leaf XYB"));
comp.Add(comp2);
root.Add(new Leaf("Leaf c")); //根部又长出叶C和D,D没长牢,被风吹跑了
Leaf leaf = new Leaf("Leaf D");
root.Add(leaf);
root.Remove(leaf);
Console.WriteLine("\n结构图:");
root.Display(1); //显示大树的样子
装饰模式
定义:动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活。
个人理解,就是一层一层地穿衣服,内裤外穿超人,内裤内穿凡人。其中此模式的顺序很重要。
优点:把类中的装饰功能从类中搬移去除,这样可以去除相关类中重复的装饰逻辑。有效的把类的核心职责和装饰功能区分开了。而且可以去除相关类中重复的装饰逻辑。它是把每个要装饰的功能放在单独的类中,并让这个类包装它所要装饰的对象。执行时,客户代码可根据需要有选择地、按顺序的使用装饰功能包装对象。
结构图:
以小菜穿衣服例子带入
抽象构件
abstract class Componnet
{
public abstract void Operation();
}
具体构件
class ConcreteComponent : Componnet
{
public override void Operation()
{
Console.WriteLine("具体对象的操作");
}
}
服饰类
abstract class Decorator : Componnet
{
protected Componnet component;
public void SetComponent(Componnet component)
{
this.component = component;
}
public override void Operation()
{
if (component !=null)
{
component.Operation();
}
}
}
具体的装饰类一
class ConcreteDecoratorA : Decorator
{
private string addedState;//本类独有的功能,以区别B
public override void Operation()
{
base.Operation();
addedState = "New state";//装饰
Console.WriteLine("具体装饰对象A的操作");
}
}
具体装饰类二
class ConcreteDecoratorB : Decorator
{
public override void Operation()
{
base.Operation();
AddedBehavior(); //装饰
Console.WriteLine("具体装饰对象b的操作");
}
private void AddedBehavior()//本类特有的方法,以区别于A
{
}
}
客户端
ConcreteComponent c = new ConcreteComponent(); //小菜
ConcreteDecoratorA d1 = new ConcreteDecoratorA();
ConcreteDecoratorB d2 = new ConcreteDecoratorB();
d1.SetComponent(c);
d2.SetComponent(d1);
d2.Operation();
外观模式
定义:为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
个人理解,两个类之间很难直接通讯,或比较复杂时,可以通过第三者来进行转发,也就是定义了一个高层接口。其符合迪米特法则。
何时使用:三个阶段,设计初期阶段,为子系统提供一个简单的接口,使得耦合大大降低。其次,开发阶段,子系统往往因为不断的重构演化而变得越来越复杂,大多数的模式使用时会产生很多很小的类,会给外部调用他们的用户程序带来使用上的困难,增加外观Facade可以提供一个简单的接口,减少他们之间的依赖。最后,当剩下一个遗留的很难维护的系统,但它的功能又很重要时,可用新系统开发一个外观Facade类,让新系统与Facade对象交互。
优点:定义的是一个新的接口,而适配器定义的是原来的接口;适配整个子系统,适配器是用来适配对象的。外观模式所针对的对象的粒度更大。
结构图:
炒股例子带入
股票的基类
class Facade
{
SubSystemOne one;
SubSystemTwo two;
public Facade()
{
one = new SubSystemOne();
two = new SubSystemTwo();
}
public void MethodA()
{
Console.WriteLine("\n方法组A()-----");
one.MthodOne();
two.MthodTwo();
}
}
股票一
class SubSystemOne
{
public void MthodOne()
{
Console.WriteLine("子系统方法一");
}
}
客户端
Facade facade = new Facade();
facade.MethodA();
享元模式
定义:运用共享技术有效的支持大量细粒度的对象
个人理解,在享元对象内部并且不会随环境改变而改变的共享部分,称为其内部状态,随环境改变而改变的,不可共享的状态就是外部状态了。
何时用:如果一个应用程序用了大量的对象,而大量的这些对象造成了很大的存储开销时就应该考虑使用;还有就是对象的的大多数状态可以外部状态,如果删除对象的外部装填,那么可以用相对较少的共享对象取代很多组对象,此时可考虑用享元模式。
优点:因为有了共享对象,实例总数就大大减少了
结构图
网站例子带入
网站抽象类
abstract class Flyweight
{
public abstract void Operation(int extrinsicstate);
}
具体网站类
class ConcreteFlyweight : Flyweight
{
public override void Operation(int extrinsicstate)
{
Console.WriteLine("具体Flyweight:"+extrinsicstate);
}
}
不需要共享的Flyweight子类
class UnsharedConcreteFlyweight : Flyweight
{
public override void Operation(int extrinsicstate)
{
Console.WriteLine("不共享的具体Flyweight:"+extrinsicstate);
}
}
网站工厂类
class FlyweightFactory
{
private Hashtable flyweights = new Hashtable(); //引入using System.Collections;
public FlyweightFactory()
{
flyweights.Add("X", new ConcreteFlyweight());
flyweights.Add("Y", new ConcreteFlyweight());
}
public Flyweight GetFlyweight(string key)
{
return ((Flyweight)flyweights[key]);//这里可作一个判断,是否存在这个对象,如果存在,直接返回,不存在,实例化后再返回
}
}
客户端
int extrinsicstate = 22;
FlyweightFactory f = new FlyweightFactory();
Flyweight fx = f.GetFlyweight("X");
fx.Operation(--extrinsicstate);
Flyweight fy = f.GetFlyweight("Y");
fy.Operation(--extrinsicstate);
Flyweight uf = new UnsharedConcreteFlyweight();
uf.Operation(--extrinsicstate);
代理模式
定义:为其他对象提供一种代理以控制对这个对象的访问。
个人理解,外卖小哥
应用:远程代理,虚拟代理,安全代理(没运用,不太明白这里的运用)
结构图
以追求者,代理送花为例子
送花的接口
abstract class Subject
{
public abstract void Requset();
}
追求者类
class RealSubject : Subject
{
public override void Requset()
{
Console.WriteLine("真是的请求");
}
}
代理类
class Proxy : Subject
{
RealSubject realSubject;
public override void Requset()
{
if (realSubject == null)
{
realSubject = new RealSubject();
}
realSubject.Requset();
}
}
客户端
Proxy proxy = new Proxy();
proxy.Requset();
不同点和相同点
代理与外观的不同:代理对象代表一个单一对象而外观代表一个子系统;代理的客户对象无法直接访问目标对象,由代理提供对单独的目标对象的访问控制,而外观的客户对象可以直接访问子系统中的各个对象,。代理是一个原来对象的代表。适配器不需要虚构一个代表者。
桥接和适配器的不同的共同特征:就是给另一对象提供一定程度的间接性,这样有利于系统的灵活性,并且都是用于软件生命周期的不同阶段。而不同之处在于桥接在设计之初就对抽象接口与它的实现部分进行桥接。
外观模式与适配器的:相同之处都是对现存系统的封装,不同的是外观定义的是一个新的接口,适配器复用一个原有的接口;并且适配器是使两个已有的接口协同工作,而外观定义的是为现存系统提供一个更为方便的访问接口。适配器是适配对象的,外观是适配整个子系统的。