首页 > 代码库 > 常用的设计模式
常用的设计模式
--什么是设计模式?设计模式有哪几种分类?每类模式重点解决什么问题?
设计模式:是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。
--根据模式的目的,将模式分为三大类:
创建型模式:抽象化了实例化过程。它们帮助一个系统独立于如何创建、组合和表示它的那些对象。
结构性模式:涉及到如何组合类和对象以获得更大的结构。结构型模式采用继承机制来组合接口或实现。
行为型模式:涉及到算法和对象间职责的分配。行为模式不仅描述对象或类的模式,还描述它们之间的通信模式。这些模式刻画了在运行时难以跟踪的复杂的控制流。
常见的几个设计模式及其用例:
一 单例模式(Singleton)
详见之前的单例模式
二.适配器模式:将一个类的接口转换成客户希望的另外一个接口。
1. 模式的问题:
Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作。
2. 解决方案:通过添加一个适配器来包装一个需要适配的对象,把原接口转换成目标接口。
3. 模式中的角色
3.1 目标接口(Target):客户所期待的接口。目标可以是具体的或抽象的类,也可以是接口。
3.2 需要适配的类(Adaptee):需要适配的类或适配者类。
3.3 适配器(Adapter):通过包装一个需要适配的对象,把原接口转换成目标接口。
4. 使用条件:
--系统需要使用现有的类,而此类的接口不符合系统的需要。
-- 想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。这些源类不一定有很复杂的接口。
--两个类所做的事情相同或相似,但是具有不同接口的时候。
-- 旧的系统开发的类已经实现了一些功能,但是客户端却只能以另外接口的形式访问,但我们不希望手动更改原有类的时候。
-- 使用第三方组件,组件接口定义和自己定义的不同,不希望修改自己的接口,但是要使用第三方组件接口的功能。
5.模式优缺点:
优点:
-- 通过适配器,客户端可以调用同一接口,因而对客户端来说是透明的。这样做更简单、更直接、更紧凑。
-- 复用了现存的类,解决了现存类和复用环境要求不一致的问题。
-- 将目标类和适配者类解耦,通过引入一个适配器类重用现有的适配者类,而无需修改原有代码。
-- 一个对象适配器可以把多个不同的适配者类适配到同一个目标,也就是说,同一个适配器可以把适配者类和它的子类都适配到目标接口。
缺点: 对于对象适配器来说,更换适配器的实现过程比较复杂。
6. 模式解读
注:在GoF的设计模式中,对适配器模式讲了两种类型,类适配器模式和对象适配器模式。由于类适配器模式通过多重继承对一个接口与另一个接口进行匹配,而C#、java等语言都不支持多重继承,因而这里只是介绍对象适配器。
4.1 适配器模式的类图
4.2 适配器模式的代码实现
/// <summary> /// 定义客户端期待的接口 /// </summary> public class Target { /// <summary> /// 使用virtual修饰以便子类可以重写 /// </summary> public virtual void Request() { Console.WriteLine("This is a common request"); } } /// <summary> /// 定义需要适配的类 /// </summary> public class Adaptee { public void SpecificRequest() { Console.WriteLine("This is a special request."); } } /// <summary> /// 定义适配器 /// </summary> public class Adapter:Target { // 建立一个私有的Adeptee对象 private Adaptee adaptee = new Adaptee(); /// <summary> /// 通过重写,表面上调用Request()方法,变成了实际调用SpecificRequest() /// </summary> public override void Request() { adaptee.SpecificRequest(); } }
4.3 客户端代码
class Program { static void Main(string[] args) { // 对客户端来说,调用的就是Target的Request() Target target = new Adapter(); target.Request(); Console.Read(); } }
运行结果:This is a special request.
三 组合模式
概述:允许你将对象组合成树型结构来表现“整体/部分”层次结构。组合能让客户以一致的方式处理个别对象以及对象组合。
1.解决的问题:将对象组合成树形结构以表示整体与部分的层次结构,使得用户对单个对象和组合对象的使用具有一致性。
2.解决方案:组合模式的关键就是引入了抽象类,它既可以代表图元,又可以代表图元的容器。在图形系统中这个类就是Gtsphic,它声明一些与特定图形对象相关的操作,如Draw。同时它也声明了所有的组合对象共享的一些操作,如一些操作用于访问和管理它的子部件。
3.使用条件:
-- 想表示对象的部分-整体层次结构。
-- 希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。
4.效果:
-- 组合模式可以很容易的增加新的组件。
-- 使用组合模式可以使客户端变的很容易设计,因为客户端可以对组合和叶节点一视同仁。
算术表达式确实是组合的例子。算术表达式包括操作数、操作符和另一个操作数。操作数可以是数字,也可以是另一个表达式。这样,2+3和(2+3)+(4*6)都是合法的表达式。
图 使用算术表达式例子的Composite模式对象图
实现:
Component:为参加组合的对象声明一个公共接口,不管是组合还是叶节点。
public abstract class Component { public void add(Component component) { throw new UnsupportedOperationException(); } public void remove(Component component) { throw new UnsupportedOperationException(); } public Component getChild(int i) { throw new UnsupportedOperationException(); } public void operation() { throw new UnsupportedOperationException(); }} Leaf:在组合中表示叶节点对象,叶节点没有子节点。public class Leaf extends Component{ public void operation() { //your code }} Composite:表示参加组合的有子对象的对象,并给出树枝构件的行为。public class Composite extends Component{ ArrayList<Component> components = new ArrayList<Component>(); public void add(Component component) { components.add(component); } public void remove(Component component) { components.remove(component); } public Component getChild(int i) { return components.get(i); } public void operation() { for (Component component : components) { component.operation(); } }}
四 工厂模式
这里指讲解一下简单工厂模式,工厂模式有三种。
简单工厂模式:
1.解决的问题:简单工厂模式根据提供给他的数据,返回几个可能类中的一个类的实例。
在软件开发中我们经常会碰到一系列相关的对象需要创建,如果按照常规做法我们就要为不同的对象创建编写不同的代码,复用性和可维护性都降低了。而且这些相关对象创建的方式也许不同,那么客户代码创建的时候就要针对不同的对象编码,对象创建的方式还是一个容易改变的地方。基于这样的情况提出了抽象工厂模式,抽象工厂模式为创建一系列相关对象提供了统一的接口,客户只要调用这个接口即可,封装了变化,隔离了变化,让客户代码稳定起来。
解决方案:通过用户调用一个工厂类来为自己生成所需对象。
5.模式UML图
五.代理模式
概述:意图是为其他对象提供一种代理以控制对这个对象的访问,简而言之就是用一个对象来代表另一个对象。
1.解决的问题:由于一些对象创建和实例化需要占用大量系统资源,但我们并不能确定用户一定会调用该对象,所以通过延迟对象实例化来减缓系统资源的消耗。
2.解决方案:用一个花费代价少的代理来代替需要的对象,直到我们确实需要这个对象时才对他进行创建和初始化。
3.种类
--远程代理,为一个对象在不同的地址空间提供局部代表
这里给出两个使用该模式的例子:
应用一:虚拟代理
例如:word文档打开
Word文档通常会含有链接、图片、表格等对象,但是并不是每次刚打开word时都要创建和实例化这些对象,特别是实例化图片对象很消耗资源。事实上,我们没必要实例化所有图片,当我们在查看word时,每次只是看到其中的一部分,所以没有必要实例化所有资源,可以使用一个虚代理物件,代替图片被载入,来加快打开文档速度,当我们看下一页时再载入图片也不迟。
类图如图所示:
如上图所示,当文档被开启时, ProxyImage代理代替RealImage物件被载入,在还没卷动至图片显示处时,也就是还没有调用 ProxyImage的Draw()时,图片并不会被载入,因而可以加速文档的开启;如果需要显示图片了, ProxyImage的 Draw()会被调用,而这时才真正创建RealImage物件,以从硬盘中载入图片。
主要类代码:
public interface Image //Image公共接口 { public abstract void Draw(); }//ProxyImage类实现Image接口public class ProxyImage implements Image { private RealImage realimage;public void Draw(){ If(realimage==NULL){ Realimage=new RealImage();}realimage.Draw();} }//RealImage类实现Image接口public class RealImage implements Image {public void Draw(){ Console.WriteLine(“载入图片……”);} }主程序public class App { public static void Main() { ProxyImage proxy = new ProxyImage(); proxy.Draw(); } }
应用二:远程访问
例子:远程数学运算访问
本地客户程序需要调用远程服务器提供的数学运算服务,也就是说数学运算服务和客户程序不在同一个地址空间之内,我们现在要面对的是跨越Internet这样一个网络障碍:这时候调用数学运算服务就没有下面那么简单了,因为我们更多的还要去考虑网络的问题,对接收到的结果解包等一系列操作。为了避免由于网络等障碍引起的复杂性,引用Proxy模式,用一个本地的代理来代替远程数学运算类打点一切,即为我们的系统引入了一层间接层,示意图如下
设计的类图如下:
如上图所示:我们在ProxMath中对实现数据类的访问,让ProxyMath来代替网络上的RealMath类,这样我们看到ProxMathy就好像是本地RealMath类,它与客户程序处在了同一地址空间内
主要代码
public interface Math //Math公共接口 { public abstract double Add(double x,double y); public abstract double Sub(double x,double y); public abstract double Mul(double x,double y); public abstract double Dev(double x,double y); }public class ProxyMath implements Math //Proxy类,继承于Math { private RealMath realmath = new RealMath(); //Proxy类中不光有调用Math类的方法,还包含一些网络通信,与远程服务器交换数据,此处省略。 public double Add(double x,double y) { return math.Add(x,y); } public double Sub(double x,double y) { return realmath.Sub(x,y); } public double Mul(double x,double y) { return realmath.Mul(x,y); } public double Dev(double x,double y) { return realmath.Dev(x,y); } }public class RealMath implements Math { public double Add(double x,double y) { return x + y; } public double Sub(double x,double y) { return x - y; } public double Mul(double x,double y) { return x * y; } public double Dev(double x,double y) { return x / y; } }测试主程序public class App { public static void Main() { ProxyMath proxy = new ProxyMath(); double addresult = proxy.Add(2,3); double subresult = proxy.Sub(2,3); double mulresult = proxy.Mul(2,3); double devresult = proxy.Dev(2,3); } }
从上面的两个例子可以概括出Proxy模式的工作原理:
首先,代理并不改变主题的接口,因为模式的用意是不让客户端感觉到代理的存在;
其次,代理使用委派将客户端的调用委派给真实的主题对象,换言之,代理起到的是一个传递请求的作用;
第三,代理在传递请求之前和之后都可以执行特定的操作(如网络通信、检查对象是否存在等),而不是单纯传递请求。
常用的设计模式