设计模式
1,设计原则
- 单一职责原则 就一个类而言,应该只有一个引起它变化的原因。如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化可能会影响到其他的职责,另外,把多个职责耦合在一起,也会影响复用性。
- 开闭原则(Open-Closed Principle) 开闭原则即OCP(Open-Closed Principle缩写)原则,该原则强调的是:一个软件实体(指的类、函数、模块等)应该对扩展开放,对修改关闭。即每次发生变化时,要通过添加新的代码来增强现有类型的行为,而不是修改原有的代码。符合开闭原则的最好方式是提供一个固有的接口,然后让所有可能发生变化的类实现该接口,让固定的接口与相关对象进行交互。
- 里氏代替原则(Liskov Substitution Principle) Liskov Substitution Principle,LSP(里氏代替原则)指的是子类必须替换掉它们的父类型。也就是说,在软件开发过程中,子类替换父类后,程序的行为是一样的。只有当子类替换掉父类后,此时软件的功能不受影响时,父类才能真正地被复用,而子类也可以在父类的基础上添加新的行为。
- 依赖倒置原则 依赖倒置(Dependence Inversion Principle, DIP)原则指的是抽象不应该依赖于细节,细节应该依赖于抽象,也就是提出的 “面向接口编程,而不是面向实现编程”。这样可以降低客户与具体实现的耦合。
- 接口隔离原则 接口隔离原则(Interface Segregation Principle, ISP)指的是使用多个专门的接口比使用单一的总接口要好。也就是说不要让一个单一的接口承担过多的职责,而应把每个职责分离到多个专门的接口中,进行接口分离。过于臃肿的接口是对接口的一种污染。
- 合成复用原则 合成复用原则(Composite Reuse Principle, CRP)就是在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分。新对象通过向这些对象的委派达到复用已用功能的目的。简单地说,就是要尽量使用合成/聚合,尽量不要使用继承。 要使用好合成复用原则,首先需要区分"Has—A"和“Is—A”的关系。“Is—A”是指一个类是另一个类的“一种”,是属于的关系,而“Has—A”则不同,它表示某一个角色具有某一项责任。导致错误的使用继承而不是聚合的常见的原因是错误地把“Has—A”当成“Is—A”.例如:
上面的设计的错误源于把“角色”的等级结构与“人”的等级结构混淆起来了,误把“Has—A”当作"Is—A"。具体的解决方法就是抽象出一个角色类:
- 迪米特法则 迪米特法则(Law of Demeter,LoD)又叫最少知识原则(Least Knowledge Principle,LKP),指的是一个对象应当对其他对象有尽可能少的了解。也就是说,一个模块或对象应尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立,这样当一个模块修改时,影响的模块就会越少,扩展起来更加容易。 关于迪米特法则其他的一些表述有:只与你直接的朋友们通信;不要跟“陌生人”说话。 外观模式(Facade Pattern)和中介者模式(Mediator Pattern)就使用了迪米特法则。
2,创建型模式
创建型模式就是用来创建对象的模式,抽象了实例化的过程。所有的创建型模式都有两个共同点:第一,它们都将系统使用哪些具体类的信息封装起来;第二,它们隐藏了这些类的实例是如何被创建和组织的。创建型模式包括:单例模式、工厂方法模式、抽象工厂模式、建造者模式和原型模式。
- 单例模式
解决的是实例化对象的个数的问题,比如抽象工厂中的工厂、对象池等,除了Singleton之外,其他创建型模式解决的都是 new 所带来的耦合关系。 单例模式指的是确保某一个类只有一个实例,并提供一个全局访问点。解决的是实体对象个数的问题,而其他的建造者模式都是解决new所带来的耦合关系问题。其实现要点有:1,类只有一个实例。问:如何保证呢?答:通过私有构造函数来保证类外部不能对类进行实例化 2,提供一个全局的访问点。问:如何实现呢?答:创建一个返回该类对象的静态方法单例模式的结构图如下所示:
private Form2
{
InitializeComponent();
}
private static Form2 SingleForm = null;
public static Form2 InitialForm()
{
if (SingleForm == null)
{
SingleForm = new Form2();
}
return SingleForm;
}
- 简单工厂模式
说到简单工厂,自然的第一个疑问当然就是什么是简单工厂模式了? 在现实生活中工厂是负责生产产品的,同样在设计模式中,简单工厂模式我们也可以理解为负责生产对象的一个类 我们平常编程中,当使用"new"关键字创建一个对象时,此时该类就依赖与这个对象,也就是他们之间的耦合度高,当需求变化时,我们就不得不去修改此类的源码,此时我们可以运用面向对象(OO)的很重要的原则去解决这一的问题,该原则就是——封装改变,既然要封装改变,自然也就要找到改变的代码,然后把改变的代码用类来封装,这样的一种思路也就是我们简单工厂模式的实现方式了。下面通过一个现实生活中的例子来引出简单工厂模式。
/// <summary>
/// 自己做饭的情况
/// 没有简单工厂之前,客户想吃什么菜只能自己炒的
/// </summary>
public class Customer
{
/// <summary>
/// 烧菜方法
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public static Food Cook(string type)
{
Food food = null;
// 客户A说:我想吃西红柿炒蛋怎么办?
// 客户B说:那你就自己烧啊
// 客户A说:好吧,那就自己做吧
if (type.Equals("西红柿炒蛋"))
{
food = new TomatoScrambledEggs();
}
// 我又想吃土豆肉丝, 这个还是得自己做
// 我觉得自己做好累哦,如果能有人帮我做就好了?
else if (type.Equals("土豆肉丝"))
{
food = new ShreddedPorkWithPotatoes();
}
return food;
}
static void Main(string[] args)
{
// 做西红柿炒蛋
Food food1 = Cook("西红柿炒蛋");
food1.Print();
Food food2 = Cook("土豆肉丝");
food2.Print();
Console.Read();
}
}
/// <summary>
/// 菜抽象类
/// </summary>
public abstract class Food
{
// 输出点了什么、菜
public abstract void Print();
}
/// <summary>
/// 西红柿炒鸡蛋这道菜
/// </summary>
public class TomatoScrambledEggs : Food
{
public override void Print()
{
Console.WriteLine("一份西红柿炒蛋!");
}
}
自己做饭,如果我们想吃别的菜时,此时就需要去买这种菜和洗菜这些繁琐的操作,有了餐馆(也就是简单工厂)之后,我们就可以把这些操作交给餐馆去做,此时消费者(也就是我们)对菜(也就是具体对象)的依赖关系从直接变成的间接的,这样就是实现了面向对象的另一个原则——降低对象之间的耦合度,下面就具体看看有了餐馆之后的实现代码(即简单工厂的实现):
/// <summary>
/// 顾客充当客户端,负责调用简单工厂来生产对象
/// 即客户点菜,厨师(相当于简单工厂)负责烧菜(生产的对象)
/// </summary>
class Customer
{
static void Main(string[] args)
{
// 客户想点一个西红柿炒蛋
Food food1 = FoodSimpleFactory.CreateFood("西红柿炒蛋");
food1.Print();
// 客户想点一个土豆肉丝
Food food2 = FoodSimpleFactory.CreateFood("土豆肉丝");
food2.Print();
Console.Read();
}
}
/// <summary>
/// 菜抽象类
/// </summary>
public abstract class Food
{
// 输出点了什么菜
public abstract void Print();
}
/// <summary>
/// 西红柿炒鸡蛋这道菜
/// </summary>
public class TomatoScrambledEggs : Food
{
public override void Print()
{
Console.WriteLine("一份西红柿炒蛋!");
}
}
/// <summary>
/// 简单工厂类, 负责 炒菜
/// </summary>
public class FoodSimpleFactory
{
public static Food CreateFood(string type)
{
Food food = null;
if (type.Equals("土豆肉丝"))
{
food= new ShreddedPorkWithPotatoes();
}
else if (type.Equals("西红柿炒蛋"))
{
food= new TomatoScrambledEggs();
}
return food;
}
}
- 工厂方法模式
工厂方法模式指的是定义一个创建对象的工厂接口,由其子类决定要实例化的类,将实际创建工作推迟到子类中。它强调的是”单个对象“的变化。其实现要点有:
1,定义一个工厂接口。问:如何实现呢?答:声明一个工厂抽象类
2,由其具体子类创建对象。问:如何去实现呢?答:创建派生于工厂抽象类,即由具体工厂去创建具体产品,既然要创建产品,自然需要产品抽象类和具体产品类了。
其具体的UML结构图如下所示:
在工厂方法模式中,工厂类与具体产品类具有平行的等级结构,它们之间是一一对应关系。
创建总工厂-提供最终对象
//总工厂负责提供所有的产品对象
interface IInLettFactory
{
Coder CreateInLettCoder();
}
实现工厂中的方法
//分工厂1提供c#程序员
class CSharpFactory : IInLettFactory
{
public Coder CreateInLettCoder()
{
return new CSharp();
}
}
程序员对象都要有自己的介绍方式
public interface Coder
{
void Information();
}
每种程序员都实现自己的介绍方式
class CSharp : Coder
{
public void Information()
{
Console.WriteLine("我是C#程序员");
}
}
class Python : Coder
{
public void Information()
{
Console.WriteLine(" I am a Python coder");
}
}
客户最终只需要向工厂负责要人就行
static void Main(string[] args)
{
while (true)
{
Console.WriteLine("请选择你需要的程序员类型:1.C#,2.Python");
string str1 = Console.ReadLine();
IInLettFactory factory = null;
if (str1 == "1")
{
factory = new CSharpFactory();
}
else if (str1 == "2")
{
factory = new PythonFactory();
}
Coder coder = factory.CreateInLettCoder();
coder.Information();
}
}
}
- 抽象工厂模式
抽象工厂模式指的是提供一个创建一系列相关或相互依赖对象的接口,使得客户端可以在不必指定产品的具体类型的情况下,创建多个产品族中的产品对象,强调的是”系列对象“的变化。其实现要点有:
1,提供一系列对象的接口。问:如何去实现呢?答:提供多个产品的抽象接口
2,创建多个产品族中的多个产品对象。问:如何做到呢?答:每个具体工厂创建一个产品族中的多个产品对象,多个具体工厂就可以创建多个产品族中的多个对象了。
具体的UML结构图如下所示:
while (true)
{
Console.WriteLine("请你选择你需要的地点程序员:1.西安,2.北京");
string str1 = Console.ReadLine();
Console.WriteLine("请你选择你需要的技术:1.Web,2.C#");
string str2 = Console.ReadLine();
InLettFactory factory = null;
if (str1 == "1")
{
factory = new XiAnInLett();
}
else if (str1 == "2")
{
factory = new BeiJingInLett();
}
Web web = null;
CSharp csharp = null;
if (str2 == "1")
{
web = factory.CreateWeb();
}
else if (str2 == "2")
{
csharp = factory.CreateCSharp();
}
if (web != null)
{
web.WebPerson();
}
if (csharp != null)
{
csharp.CSharpPerson();
}
}
抽象工厂
/// <summary>
/// 总集团公司
/// </summary>
public abstract class InLettFactory
{
public abstract CSharp CreateCSharp();
public abstract Web CreateWeb();
}
实现工厂
//西安的子公司
class XiAnInLett : InLettFactory
{
public override CSharp CreateCSharp()
{
return new XiAnCSharp();
}
public override Web CreateWeb()
{
return new XiAnWeb();
}
}
//北京的子公司
class BeiJingInLett : InLettFactory
{
public override CSharp CreateCSharp()
{
return new BeiJingCSharp();
}
public override Web CreateWeb()
{
return new BeiJingWeb();
}
}
C#抽象对象
//提供一个CSharp程序员
public abstract class CSharp
{
public abstract void CSharpPerson();
}
实现C#抽象对象
//提供一个西安的CSharp程序员
class XiAnCSharp : CSharp
{
public override void CSharpPerson()
{
Console.WriteLine("我是西安的C#程序员");
}
}
//提供一个北京的CSharp程序员
class BeiJingCSharp : CSharp
{
public override void CSharpPerson()
{
Console.WriteLine("我是北京的C#程序员");
}
}
Web抽象对象
//提供一个Web程序员
public abstract class Web
{
public abstract void WebPerson();
}
实现Web抽象对象
//提供一个西安的Web程序员
class XiAnWeb : Web
{
public override void WebPerson()
{
Console.WriteLine("我是西安的WEB工程师");
}
}
//提供一个北京的Web程序员
class BeiJingWeb : Web
{
public override void WebPerson()
{
Console.WriteLine("我是北京的WEB程序员");
}
}
抽象工厂的分析
抽象工厂模式将具体产品的创建延迟到具体工厂的子类中,这样将对象的创建封装起来,可以减少客户端与具体产品类之间的依赖,从而使系统耦合度低,这样更有利于后期的维护和扩展,这真是抽象工厂模式的优点所在,然后抽象模式同时也存在不足的地方。下面就具体看下抽象工厂的缺点(缺点其实在前面的介绍中以已经涉及了):
抽象工厂模式很难支持新种类产品的变化。这是因为抽象工厂接口中已经确定了可以被创建的产品集合,如果需要添加新产品,此时就必须去修改抽象工厂的接口,这样就涉及到抽象工厂类的以及所有子类的改变,这样也就违背了“开发——封闭”原则。
知道了抽象工厂的优缺点之后,也就能很好地把握什么情况下考虑使用抽象工厂模式了,下面就具体看看使用抽象工厂模式的系统应该符合那几个前提:
a, 一个系统不要求依赖产品类实例如何被创建、组合和表达的表达,这点也是所有工厂模式应用的前提。
b,这个系统有多个系列产品,而系统中只消费其中某一系列产品
c, 系统要求提供一个产品类的库,所有产品以同样的接口出现,客户端不需要依赖具体实现。
- 原型工厂模式
原型模式指的是通过给出一个原型对象来指明所要创建的对象类型,然后用复制的方法来创建出更多的同类型对象。其实现要点有:
1,给出一个原型对象。问:如何办到呢?答:很简单嘛,直接给出一个原型类就好了。
2,通过复制的方法来创建同类型对象。问:又是如何实现呢?答:.NET可以直接调用MemberwiseClone方法来实现浅拷贝
具体的UML结构图如下所示:
原型模式的优点有
1,原型模式向客户隐藏了创建新实例的复杂性
2,原型模式允许动态增加或较少产品类。
3原型模式简化了实例的创建结构,工厂方法模式需要有一个与产品类等级结构相同的等级结构,而原型模式不需要这样。
4,产品类不需要事先确定产品的等级结构,因为原型模式适用于任何的等级结构
原型模式的缺点有
1,每个类必须配备一个克隆方法配备克隆方法
2,需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。 - 建造者模式
建造者模式指的是将一个产品的内部表示与产品的构造过程分割开来,从而可以使一个建造过程生成具体不同的内部表示的产品对象。强调的是产品的构造过程。其实现要点有:
将产品的内部表示与产品的构造过程分割开来。问:如何把它们分割开呢?答:不要把产品的构造过程放在产品类中,而是由建造者类来负责构造过程,产品的内部表示放在产品类中,这样不就分割开了嘛。
具体的UML结构图如下所示:
3,结构型模式
结构型模式,顾名思义讨论的是类和对象的结构,主要用来处理类或对象的组合。它包括两种类型,一是类结构型模式,指的是采用继承机制来组合接口或实现;二是对象结构型模式,指的是通过组合对象的方式来实现新的功能。它包括适配器模式、桥接模式、装饰者模式、组合模式、外观模式、享元模式和代理模式。
4行为型模式
行为型模式是对在不同对象之间划分责任和算法的抽象化。行为模式不仅仅关于类和对象,还关于它们之间的相互作用。行为型模式又分为类的行为模式和对象的行为模式两种。 - 类的行为模式——使用继承关系在几个类之间分配行为。
- 对象的行为模式——使用对象聚合的方式来分配行为。
行为型模式包括11种模式:模板方法模式、命令模式、迭代器模式、观察者模式、中介者模式、状态模式、策略模式、责任链模式、访问者模式、解释器模式和备忘录模式。
- 模板方法模式:封装算法结构,定义算法骨架,支持算法子步骤变化
- 命令模式:注重将请求封装为对象,支持请求的变化,通过将一组行为抽象为对象,实现行为请求者和行为实现者之间的解耦
- 迭代器模式:注重封装特定领域变化,支持集合的变化,屏蔽集合对象内部复杂结构,提供客户程序对它的透明遍历。
- 观察者模式:注重封装对象通知,支持通信对象的变化,实现对象状态改变,通知依赖它的对象并更新。
- 中介者模式:注重封装对象间的交互,通过封装一系列对象之间的复杂交互,使他们不需要显式相互引用,实现解耦。
- 状态模式:注重封装与状态相关的行为,支持状态的变化,通过封装对象状态,从而在其内部状态改变时改变它的行为。
- 策略模式:注重封装算法,支持算法的变化,通过封装一系列算法,从而可以随时独立于客户替换算法。
- 责任链模式:注重封装对象责任,支持责任的变化,通过动态构建职责链,实现事务处理。
- 访问者模式:注重封装对象操作变化,支持在运行时为类结构添加新的操作,在类层次结构中,在不改变各类的前提下定义作用于这些类实例的新的操作。
- 备忘录模式:注重封装对象状态变化,支持状态保存、恢复。
- 解释器模式:注重封装特定领域变化,支持领域问题的频繁变化,将特定领域的问题表达为某种语法规则下的句子,然后构建一个解释器来解释这样的句子,从而达到解决问题的目的。