13
如如如 .Net如如如如如如如 如如 rupeng.com 设设设设 Design Pattern 如如如 如如 Computer 如如 Destroy 如 如如 IDisposable 如如 如如如如 Computer 如如如如如如如如 如如如 using 如 如 如如 一。 “如”如 如 如“如” “如如” “如如”如 如如如如如 如如 如如 ,,、 一,,。 GOF 如如如 、、、MVCN如 如…… 如如如如如如如如如 一,一,。 “如 “如 如如如如如如如如“如如” 如如如如如如如如 ,。

static.rupeng.com€¦  · Web view例子:编写一个TextParser,他对于传入的文件解析他的文件的纯文本内容,文件可能是txt、html、word、pdf ... 先实现txt、html的

Embed Size (px)

Citation preview

Page 1: static.rupeng.com€¦  · Web view例子:编写一个TextParser,他对于传入的文件解析他的文件的纯文本内容,文件可能是txt、html、word、pdf ... 先实现txt、html的

如鹏网《.Net 设计模式公开课》课件 rupeng.com

设计模式 Design Pattern

思考题:假如有一个 Computer 类有 Destroy 方法,但是没有实现 IDisposable 接口,在不改变 Computer 类的情况下怎么样能使用 using 进行资源的回收。

用适配器模式实现一下。“设计模式”的概念是来自于建筑领域,建筑有很多套路,比如“中式建筑”、“欧式

建筑”、“地中海风格建筑”等,每个风格都有一个套路,每个套路都有已经前人总结下来的经验,按照这个套路设计房子就会简单很多。GOF

面向过程、面向对象、面向切面、MVC、N 层架构、微服务架构……一切的目的都是以合适的方式组织代码,效果是一样的效果,不同的组织形式有不同的优缺点。

设计模式就是“套路”,就是“别人的成功故事”,所以不应该简单的“复制”,而应该灵活运用。

Page 2: static.rupeng.com€¦  · Web view例子:编写一个TextParser,他对于传入的文件解析他的文件的纯文本内容,文件可能是txt、html、word、pdf ... 先实现txt、html的

如鹏网《.Net 设计模式公开课》课件 rupeng.com

设计模式只是原则,不是具体的技术;有的设计模式有固定的代码模式,有的则没有,只是一个“想法”而已。

学设计模式千万别看 GOF 的《设计模式》,这是设计模式在软件行业应用的开山之所,但是这本书是 GOF 的论文,看不懂很正常,能看懂的都是奇人。

目前也没有一本讲的很通俗易懂的设计模式的书或者系列文章,有一些文章看似有代

Page 3: static.rupeng.com€¦  · Web view例子:编写一个TextParser,他对于传入的文件解析他的文件的纯文本内容,文件可能是txt、html、word、pdf ... 先实现txt、html的

如鹏网《.Net 设计模式公开课》课件 rupeng.com

码、有例子,但是全是用“车、动物、开车”这种没有实际意义的代码去讲,好像听懂了但是有没有感觉有啥用。而且没有业务逻辑,体现不出来设计模式的好处,让人看了只会质疑“有必要这样吗?”

架构设计是一个有经验、因项目而异、因人而异的,就像做中餐的“少许”、“适量”一样。架构设计也有很多“尽量不要……”、“推荐……”。

虽然说房子有固定的建筑风格,但是不同的房子仍然有细节的不同,有一些房子也很难说是什么风格。

同样的,设计模式只是一个大概的套路,不能生搬硬套,有一些做法可能是多个设计模式的融合,有的也很难归为某个设计模式。不要为了设计模式而设计模式,“忘了设计模式”才是终极目标。

复习面向对象

面向对象三大特征:封装、继承、多态。除了单例模式、享元模式、备忘录模式等几个之外,其他设计模式都主要是“多态”的应用,因此要重点复习一下“多态”

多态:父类类型的变量可以指向子类类型的对象,调用方法的时候调用的是子类的实现。不懂多态,别学设计模式。

下面的程序的执行结果是什么?class Program{

static void Main(string[] args){

Person p1 = CreatePerson(1);p1.Hello();CreatePerson(2).Hello();CreatePerson(3).Hello();

Console.ReadKey();}public static Person CreatePerson(int i){

if (i == 1){

return new Chinese();}

Page 4: static.rupeng.com€¦  · Web view例子:编写一个TextParser,他对于传入的文件解析他的文件的纯文本内容,文件可能是txt、html、word、pdf ... 先实现txt、html的

如鹏网《.Net 设计模式公开课》课件 rupeng.com

else if (i == 2){

return new Japanese();}else{

return new Person();}

}}

class Person{

public virtual void Hello(){

Console.WriteLine("我是 Person");}

public Person Dad { get; set; }

public void DadHello(){

if (this.Dad == null){

Console.WriteLine("我没有 dad");}else{

this.Dad.Hello();}

}}

class Chinese : Person{

public override void Hello(){

Console.WriteLine("我是 Chinese");}

}

class Japanese : Person{

Page 5: static.rupeng.com€¦  · Web view例子:编写一个TextParser,他对于传入的文件解析他的文件的纯文本内容,文件可能是txt、html、word、pdf ... 先实现txt、html的

如鹏网《.Net 设计模式公开课》课件 rupeng.com

}

下面的代码呢:Person p1 = CreatePerson(1); //ChinesePerson p2 = CreatePerson(2); //Japanesep2.Dad=p1; //p2 的 dad指向了 Chinese 对象p2.DadHello();p2.Hello();执行结果是什么?

面向对象设计原则

一、 单一职责原则一个类的功能要单一,不要“瑞士军刀”的类。

二、 开闭原则对扩展开放,对修改关闭。通俗的说:需要修改业务逻辑的时候,只要对系统做扩展

即可,不需要修改已有代码。ASP.Net MVC 中的 Filter 就体现了开闭原则:给网站增加权限校验功能,不需要修改已

有代码,只要增加一个自定义 Filter即可。三、 里氏替换原则任何父类可以出现的地方,子类一定可以出现。或者用如鹏课程的话语体系是:父类

类型的变量可以指向子类类型的对象,调用方法的时候执行的是子类的实现。

Page 6: static.rupeng.com€¦  · Web view例子:编写一个TextParser,他对于传入的文件解析他的文件的纯文本内容,文件可能是txt、html、word、pdf ... 先实现txt、html的

如鹏网《.Net 设计模式公开课》课件 rupeng.com

StreamReader 类的构造函数 public StreamReader(Stream stream)体现的就是里氏替 换 原 则 , 可 以 传 入 任 何 Stream 的 子 类 对 象MemoryStream、FileStream、NetworkStream、BufferedStream 等,也可以自定义一个类继承自 Stream,然后传进去。违反里氏替换原则的代码:public void saveImage(Object value){ if(value is Stream) { //….. } else if(value is FileInfo) { //…. } else { throw new AAException("不支持的 value 类型");

}}

四、 依赖倒置原则通俗的说就是:变量、参数、返回值类型尽量用接口/抽象类,而不用具体类,能用父接口/类,就不用子接口/类。假如提供一个从流中读取文本文件内容的方法:public static String ReadAllText(FileStream stream){ //}这样就只能传递 FileStream 或其子类对象。如果把参数类型改成 Stream 类型:public static String ReadAllText(Stream stream)这样所有的 Stream 子类对象都能传进来。

五、 接口隔离原则六、 迪米特原则/最少知识原则七、 合成聚合原则

优先使用组合,而不是继承。也就是重用一个类的成员的时候,尽量不要声明他的子类,而是组合。

继承的坏处:如果直接继承基类,会破坏封装,因为继承将基类的实现细节暴露给子类;如果基类的实现发生改变,则子类的实现也不得不发生改变。

”Has—A”(组合)和“Is—A”(继承)。例如:鸡是动物,这就是“ Is-A”的表现;某人有一个手机,Person 类型里面包含一个 Phone 类型,这就是“Has-A”。

比如设计一个 ClassRoom 类,可以增加 Person,可以写一个 ClassRoom 类继承自 List:class ClassRoom:List<Person>

Page 7: static.rupeng.com€¦  · Web view例子:编写一个TextParser,他对于传入的文件解析他的文件的纯文本内容,文件可能是txt、html、word、pdf ... 先实现txt、html的

如鹏网《.Net 设计模式公开课》课件 rupeng.com

{

}

这样做虽然简单,但是会引入问题,这个 ClassRoom 类就引入了太多不知所云的方法以及很难控制的方法,比如要想控制“一个班只能加入 50 个人”就很困难。

改造成组合的形式:class ClassRoom{

private List<Person> persons = new List<Person>();

public void Add(Person p){

if(persons.Count>=50){

throw new Exception("最多 50 个");}persons.Add(p);

}}

策略模式

英文名称:Stragety Pattern干一件事情的时候有多个类似的算法,在不同情况下用不同的算法。比如计算一个字符串加盐之后的散列值,以及比较一个散列值是否正确。散列算法有

MD5、MD4、SHA1、SHA256、SHA512 等。比如:

public enum HashType {MD5,SHA1,SHA256

}

using System;using System.Security.Cryptography;using System.Text;

namespace jimi{

Page 8: static.rupeng.com€¦  · Web view例子:编写一个TextParser,他对于传入的文件解析他的文件的纯文本内容,文件可能是txt、html、word、pdf ... 先实现txt、html的

如鹏网《.Net 设计模式公开课》课件 rupeng.com

class Test { private String salt = "rupeng.com"; private HashType hashType; public Test(HashType hashType) { this.hashType = hashType; }

public String CalcStringHash(String value) { if (hashType == HashType.MD5) { // Use input string to calculate MD5 hash MD5 md5 = System.Security.Cryptography.MD5.Create(); byte[] inputBytes = System.Text.Encoding.ASCII.GetBytes(value + salt); byte[] hashBytes = md5.ComputeHash(inputBytes);

// Convert the byte array to hexadecimal string StringBuilder sb = new StringBuilder(); for (int i = 0; i < hashBytes.Length; i++) { sb.Append(hashBytes[i].ToString("X2")); } return sb.ToString(); } else if (hashType == HashType.SHA1) { Encoding encode = Encoding.UTF8; SHA1 sha1 = new SHA1CryptoServiceProvider(); byte[] bytes_in = encode.GetBytes(value + salt); byte[] bytes_out = sha1.ComputeHash(bytes_in); sha1.Dispose(); string result = BitConverter.ToString(bytes_out); result = result.Replace("-", ""); return result; } else if (hashType == HashType.SHA256) { byte[] bytValue = System.Text.Encoding.UTF8.GetBytes(value + salt); SHA256 sha256 = new SHA256CryptoServiceProvider(); byte[] retVal = sha256.ComputeHash(bytValue); StringBuilder sb = new StringBuilder(); for (int i = 0; i < retVal.Length; i++)

Page 9: static.rupeng.com€¦  · Web view例子:编写一个TextParser,他对于传入的文件解析他的文件的纯文本内容,文件可能是txt、html、word、pdf ... 先实现txt、html的

如鹏网《.Net 设计模式公开课》课件 rupeng.com

{ sb.Append(retVal[i].ToString("x2")); } return sb.ToString(); } else { throw new Exception("未知的散列算法"); } }

public bool CheckStringHash(String hashValue, String value) { String hashValue2 = CalcStringHash(value); return hashValue2.Equals(hashValue,StringComparison.OrdinalIgnoreCase); } }}

测试一下:Test t = new Test(HashType.MD5);String hash = t.CalcStringHash("123456");Console.WriteLine(hash);Console.WriteLine(t.CheckStringHash("d318dcc7b247780e119e0f5d3e687989", "123456"));Console.WriteLine(t.CheckStringHash("e318dcc7b2x7780e119e0f5d3e687989", "123456"));Console.ReadKey();

如果要增加对新加密算法的支持,就要对于枚举类型增加新的值,并且修改CalcStringHash 方法中的 if,不符合“开闭原则”。如下改造。interface IDigestStragety{

String Digest(String data);}

class Test2{

private String salt = "rupeng.com";private IDigestStragety digestStrgety;

public Test2(IDigestStragety digestStrgety){

this.digestStrgety = digestStrgety;}

Page 10: static.rupeng.com€¦  · Web view例子:编写一个TextParser,他对于传入的文件解析他的文件的纯文本内容,文件可能是txt、html、word、pdf ... 先实现txt、html的

如鹏网《.Net 设计模式公开课》课件 rupeng.com

public String CalcStringHash(String value){

return digestStrgety.Digest(value + salt);}

public bool CheckStringHash(String hashValue, String value){

String hashValue2 = CalcStringHash(value);return

hashValue2.Equals(hashValue,StringComparison.OrdinalIgnoreCase);}

}

class MD5Stragety : IDigestStragety{

public string Digest(string data){

// Use input string to calculate MD5 hashMD5 md5 = System.Security.Cryptography.MD5.Create();byte[] inputBytes = System.Text.Encoding.ASCII.GetBytes(data);byte[] hashBytes = md5.ComputeHash(inputBytes);

// Convert the byte array to hexadecimal stringStringBuilder sb = new StringBuilder();for (int i = 0; i < hashBytes.Length; i++){

sb.Append(hashBytes[i].ToString("X2"));}return sb.ToString();

}}

class SHA1Stragety : IDigestStragety{

public string Digest(string data){

Encoding encode = Encoding.UTF8;SHA1 sha1 = new SHA1CryptoServiceProvider();byte[] bytes_in = encode.GetBytes(data);byte[] bytes_out = sha1.ComputeHash(bytes_in);sha1.Dispose();string result = BitConverter.ToString(bytes_out);result = result.Replace("-", "");

Page 11: static.rupeng.com€¦  · Web view例子:编写一个TextParser,他对于传入的文件解析他的文件的纯文本内容,文件可能是txt、html、word、pdf ... 先实现txt、html的

如鹏网《.Net 设计模式公开课》课件 rupeng.com

return result;}

}测试代码:Test2 t = new Test2(new MD5Stragety());String hash = t.CalcStringHash("123456");Console.WriteLine(hash);Console.WriteLine(t.CheckStringHash("d318dcc7b247780e119e0f5d3e687989", "123456"));Console.WriteLine(t.CheckStringHash("d318dcc7b2x7780e119e0f5d3e687989", "123456"));Console.ReadKey();

这样只要创建 Test2 的时候只要传递不同的 IDigestStragety 实现类即可,如果增加新的散列算法,用户只要自定义类实现 IDigestStragety 接口,然后提供进去就行了。消灭了 if,符合“开闭原则”。

适配器模式

英文名称:Adapter Pattern如果需要将“一些现存的对象”放在新的环境中应用,但是新环境要求的接口是这些现存

对象所不满足的。那么就可以创建一个适配器(转换器)让旧对象适用于新环境。生活中的例子:

Page 12: static.rupeng.com€¦  · Web view例子:编写一个TextParser,他对于传入的文件解析他的文件的纯文本内容,文件可能是txt、html、word、pdf ... 先实现txt、html的

如鹏网《.Net 设计模式公开课》课件 rupeng.com

USB电池

安卓线转苹果线 AV线转HDMI线

假如有一个 Computer 类有 Destroy 方法,但是没有实现 IDisposable 接口,在不改变Computer 类的情况下怎么样能使用 using 进行资源的回收。

Page 13: static.rupeng.com€¦  · Web view例子:编写一个TextParser,他对于传入的文件解析他的文件的纯文本内容,文件可能是txt、html、word、pdf ... 先实现txt、html的

如鹏网《.Net 设计模式公开课》课件 rupeng.com

职责链模式

英文名:Chain of Responsibility Pattern“一个请求可以被多个对象处理,但是每个请求在运行时只能有一个接受者,如果显示指

定,将必不可少地带来请求发送者与接受者的紧耦合。如何使请求的发送者不需要指定具体的接受者,让请求的接受者自己在运行时决定来处理请求,从而使两者解耦。”

一句话:挨个问“你能干这件事吗?”,碰到的第一个回答“能”的人来处理。例子:编写一个 TextParser,他对于传入的文件解析他的文件的纯文本内容,文件可能

是 txt、html、word、pdf、excel 等文档,也能根据需要增加新的文件类型的处理。public interface ITextReader {

/** * 给定的文件 file 是否能被这个类处理 */bool Accept(String filePath);

/** * 读取文件 file,读取他的文本内容 */String ReadAsString(String filePath);

}

编写 TextParser,反射获取注册所有实现了 ITextReader 的类来加载 ITextReader。然后提供 public static String ReadAsString(String filePath)先实现 txt、html 的读取。也可以增加 doc 的处理。

其他实现类也这样修改,这就是设计模式的魔力。可以尝试再去添加对 excel、pdf 等文件的支持,我们就是标准的制定者

装饰者模式

英文名称:Decorator Pattern如果我们想给一些实现类加多个额外的功能,这些功能不是每个类的应用场景都要,而且

这些额外的功能不是每个场景都需要,A场景需要 1、5 功能,B场景需要 2、5、6 功能。而且不能修改类的原有实现,那么就可以实现装饰者模式。

装饰者类实现的是和被装饰的类相同的接口,可以代替被装饰的类出现,主要功能还是转

Page 14: static.rupeng.com€¦  · Web view例子:编写一个TextParser,他对于传入的文件解析他的文件的纯文本内容,文件可能是txt、html、word、pdf ... 先实现txt、html的

如鹏网《.Net 设计模式公开课》课件 rupeng.com

发给具体实现类实现,只有特殊功能才由自己实现。比如有时候我们需要“只读的 List”,也就是不能 add、remove 的 List:

static IList<String> GetData(){

List<String> list = new List<>();list.add("abc");list.add("123");list.add("如鹏网");list.add("rupeng.com");list.add("qq");return list;

}使 用 者 如 果拿到返回值之后还能 Add 、 Remove 会 不 好 , 这时候可 以 定 义 一 个

ReadOnlyList 类:public class ReadOnlyList<T> :IList<T> {

private IList<T> target;

public ReadOnlyList(IList<T> target) {this.target = target;

}...}

只要把GetData()最后一行改一下即可:static IList<String> getData(){

List<String> list = new List<String>();list.add("abc");list.add("123");list.add("如鹏网");list.add("rupeng.com");list.add("qq");return new ReadOnlyList<String>(list);

}由于 ReadOnlyList 也实现了 List 接口,所以其他代码不会有任何的变化,但是就把

list“装饰”起来了。按照多态分析一下运行过程。这也是基于接口编程、变量尽量写成接口类型的好处。

再开发几个装饰器类:限制集合最大大小的 SizeLimitList:限制字符串长度的 StringLenLimitList:如果只需要限制最大数据条数的 List:

IList<String> list = new SizeLimitList<>(new LinkedList<>(), 10)

Page 15: static.rupeng.com€¦  · Web view例子:编写一个TextParser,他对于传入的文件解析他的文件的纯文本内容,文件可能是txt、html、word、pdf ... 先实现txt、html的

如鹏网《.Net 设计模式公开课》课件 rupeng.com

如果需要既限制最大数据条数又限制字符串长度的 LinkedList:List<String> list = new StringLenLimitList(new SizeLimitList<>(new

LinkedList<>(), 10), 8);装饰器使用非常灵活。