《设计模式之禅》读书笔记之C#版-创建类模式

备注:由于读的电子书版本是pdf的,影印都有些看不清楚。所有练习代码都单独放到了GitHub上方便以后查看。
https://github.com/yuhezhangyanru/DesignPatternDemoCollection

注:本书主要是针对Java讲的,涉及到语法特性的时候,每种语言特性不尽相同,不能一概而论–作者 秦晓波

首先必须说明看这本书的过程中越看越觉得棒!第一遍看的时候还不怎么懂,尤其你如果常用语言非Java的话,光看更没有什么用,最好是遇到的例子用自己习惯的语言再实现一边,会发现书中阐述的道理真的非常实用!

六大设计原则:

- (1)单一职责原则(SingleResposibilityPrinciple),简称SRP。

要求一个类或接口只有一个原因引起变化,也就是只负责一件事情。
单一职责的好处:
降低类的复杂性,定义清晰明确;
提高可读性;
提高可维护性;
降低修改引起的风险;
但是具体怎么划分职责还需要具体项目实际考虑。
单一职责适用于接口、类,同时也可以用在方法中,一个方法尽可能只做一件事情。类的设计方法尽量做到只有一个原因引起变化。

- (2)里式替换原则。LiskovSubstitution Principle,简称 LSP。为了实现更好的继承方式。

简单定义:所有引用基类的地方必须能透明的使用其子类的对象。
换句话说,只要父类出现的地方,就可以出现子类,而且替换成子类也不会产生任何异常的。使用者并不知道用的父类还是子类,但反过来就不行了,使用子类的时候,父类未未必适应。因为子类的成员和方法可能多余父类。
该原则定义的规范:
1,子类必须完全实现父类方法:设计系统用到一个接口或抽象类的时候,一般在调用类中直接传入接口或父类对象而非子类,调用的人不需要关心具体调用的是什么类型的子类

public class Father
{
   public virtual void testFunc(Father obj)
   //此处传入的类型尽量为Father类型,外部调用
   {
   }
}

public class Child2:Father
{
}

public class Child:Father
{ 
   //此处传入的类型尽量为Father类型,外部调用时不用关心具体是Child还是Child2类型的
   public override void testFunc(Father obj)
   {
   }
}

2,子类可以有自己的个性,但注意的是向下转型不安全,即子类强转为父类的时候。所以子类出现的地方未必可以出现父类
3,覆盖或实现父类方法时输入参数可以被放大..呵呵,这条原则建议慎用额!
C#和Java的继承还是有差别的 TODO page15中的例子,用C#和java重新实现一遍,看看书里面说的是不是对的!

- (3) 依赖倒置原则,Dependence Inversion Principle,DIP

具体含义:
1. 高层模块不应该依赖低层模块,两者都应该依赖其抽象
2. 抽象不应该依赖细节
3. 细节应该依赖抽象
注:不可分割的原子模块就是低层模块,原子逻辑再组装就是高层模块。在Java中,抽象指接口或抽象类,不能被直接实例化的,细节就是实现类(子类)。依赖倒置在Java中的表现,我就不说实现抽象之类的了,头疼,直接简单描述为子类父类了:
1. 模块间通过依抽象来产生依赖,子类不直接产生依赖,其依赖关系通过接口或父类产生;
2. 接口或父类不依赖与子类;
3. 子类依赖接口或父类;
这也是面向接口编程的思想(Object Oriented-Design OOD)之一
本质:
- 通过抽象(接口或)

- (4) 接口隔离原则

首先看定义,java中接口的含义分为以下两种:
- 实例接口,属于抽象类,类似于C++中的父类;
- 类接口,通过interface关键字定义的接口;
“隔离”的含义:
- 客户端不依赖不需要的接口,即避免冗余的依赖;
- 类间的依赖关系应建立在最小接口上,要求接口的功能细化;

简单来说,设计简单的接口,避免臃肿庞大的接口;

page30接口和抽象类使用例子:见笔记:C#接口和抽象类使用

- (5) 迪米特法则

(Low of Demeter, LoD)又称最少知识原则,通俗说尽量减少类之间的耦合度,
减少类的public方法
如果一个方法放在本类中,既不增加类间关系,也对本类没有负面影响,那就放置在本类中

- (6) 开闭原则

类、模块和函数应该对扩展开放,对修改关闭。通俗讲是尽量通过扩展已有的行为来实现变化而非直接修改已有代码

具体设计模式的学习和案例

单例模式:

1.单例模式:

需求及使用场景:
- 项目中要求只有一个对象时
- 创建对象消耗太多或频繁创建销毁
- 或包含大量静态常量/静态方法

++确保只有一个实例++

优点:
- 减少内存开支
- 避免对资源的多重占用
- 可以在系统设置全局的访问点
- 优化和共享资源访问
缺点:
- 一般没有接口,扩展困难
- 与单一职责原则有冲突

using System;
//单例模式的例子:只允许外部获取一个实例对象
public class Emperor
{
 private static Emperor emperor = new Emperor();  

//把构造函数指定为私有的,外部就不能实例化了
 private Emperor() 
 {
 }

 public static Emperor getInstance()
 {
  return emperor;
 }

 public void say()
 {
  System.Console.WriteLine("我是皇帝");
 }
}

public class Client
{
 public static void Main(string[] args)
 {
  Emperor emperor= Emperor.getInstance();
  emperor.say();
 }

上面的例子是正常的,但是下面的例子在高并发的时候会存在线程不安全,可能产生多个实例:

public static Singleton getInstance()
{
    if(singleton==null)
    {
         singleton =new Singleton();
    }
    return singleton;
}

TODO 详细的线程安全见7-3的例子


工厂方法模式

需要注意的是:在接口方法的声明中,书中以Java为例的接口成员都指明了public类型,但不是所有语言都能指定public,如C#就不需要指定已经强制为public,直接声明成员即可。

定义:

定义一个用户创建对象的接口,让子类决定要实例化哪个类,适用于需要灵活/可扩展的框架

优缺点:

优点:
- 良好封装性,不用关系具体类是如何实例化的
- 创建对象有条件约束,如知道类名或基类型
- 可扩展性强,增加子类型不需要修改工厂

demo举例:


    //抽象工厂接口类
    public abstract class AbstractHumanFactory
    {
        public abstract T createHuman<T>();
    }

    //具体生产Human类型的工厂子类
    public class HumanFactory : AbstractHumanFactory
    {
        public override T createHuman<T>( )
        {
            Human human = null;
            try
            {
                human = (Human)Activator.CreateInstance(typeof(T));
            }
            catch (Exception e)
            {
                Console.WriteLine("excep=" + e.ToString());
            }
            return (T)human;
        }
    }
//具体Human基类型:声明为interface
  public interface Human
    {
        void getColor();

        void talk();
    }
    //子类实现举例
       public class WhiteHuman : Human
    {
        public void getColor()
        {
            Console.WriteLine("白色人种 皮肤白色");
        }

        public void talk()
        {
            Console.WriteLine("白色人种 说单字节");
        }
    }

最后补充测试demo:

 private static void CreateHumanTest()
   {
        AbstractHumanFactory yinyanglu = new HumanFactory();
        Human whiteHuman = yinyanglu.createHuman<WhiteHuman>();
        whiteHuman.getColor();
        whiteHuman.talk();

        Human blackHuman = yinyanglu.createHuman<BlackHuman>();
        blackHuman.getColor();
        blackHuman.talk();

        Human yellowHuman = yinyanglu.createHuman<YellowHuman>();
        yellowHuman.getColor();
        yellowHuman.talk();

        //注意:yinyanglu已经限制为人Human及子类型,TestDeleteClass普通类实例化会报错
        //yinyanglu.createHuman<TestDeleteClass>();
    }
注意:以上的demo可以作为在C#中的工厂框架来使用,但是原书中的Product抽象工厂demo,我尝试了在C#中是不行的,也就是说在C#中抽象接口类实例化的基可类型得是接口interface(即Human是interface),否则如果Human是抽象类例如如下声明的话:
  public abstract class Human
    {
        public void method1()
        {

        }
        public abstract void method2();
    }

在抽象工厂HumanFactory方法中实例化完毕要返回时转换数据类型编译不过,即return (T)human这一步无法通过,如果想混顺利执行的话,工厂返回的对象类型就不能使泛型了必须指明实例化类型是父类Human。

  public class HumanFactory : AbstractHumanFactory
    {
       public override Human createHuman<Human>()
        {
            Human human1 = default(Human);
            try
            {
                human1 = (Human)Activator.CreateInstance(typeof(Human));
            }
            catch (Exception e)
            {
                Console.WriteLine("excep=" + e.ToString());
            }
            return human1;
        }
    }

为了全面对照,由于我不熟悉Java的抽象类和接口有什么区别,按照定义就查了一下,
这个作者的分享写的非常好,赞一个
其实在C#里大部分也是如此:

抽象类:
- Java中的抽象类就是“包含”抽象方法的类(成员为public或protected),“包含”二字很重要,未必一定有抽象方法,也未必方法都是抽象方法,抽象方法只能声明不能实现,但普通方法可以实现;
- 不能直接用来实例化,是为了让子类继承;
- 继承了抽象类的子类必须实现(即覆写)所有抽象方法;
- 子类只能继承一个抽象类,目前我知道只有C++支持多继承,C#多继承也是编译不过的;

接口类:
- 所有成员都是public(C#中直接声明定义,不能指定修饰符);
- 所有“成员”都是抽象方法;
- 不能包含静态方法;
- 子类可以实现多个接口;

工厂方法的扩展:
简单工厂模式:

demo:

    public class HumanFactorySimple
    {
        //不用实例工厂,直接用static
        public static T createHuman<T>()
        {
            Human human = null;
            try
            {
                human = (Human)Activator.CreateInstance<T>();
            }
            catch (Exception e)
            {
                Console.WriteLine("e=" + e.ToString());
            }
            return (T)human;
        }
    }
多个工厂模式:

属于复杂工厂模式,简单来说就是一个实例化类对应一个构造工厂
多工厂的基类:

   public abstract class AbstractHumanFact1
    {
        public abstract Human createHuman();
    }

子工厂实现:

  public class HumanYellowFactory : AbstractHumanFact1
    {
        public override Human createHuman()
        {
            return new YellowHuman();
        }
    }

实例化对象的使用:

   Human yellow1 = (new HumanYellowFactory()).createHuman();
   yellow1.talk();
替代单例模式:

专门定义一个工厂来来创建单例,但是在C#中实现可能会“啰嗦”一点,这个工厂创建单例的基本思路是类的声明构造指定为private即外部无法直接new一个出来,然后定义一个工厂反射获得类的默认构造来实例化一个,当然“实例化”的动作又放在工厂自己的静态构造里,保障不被多次执行。这么啰嗦,以下是这个工厂的实现demo。

关于C#访问默认构造,我借鉴的这篇文章

被实例化的单例类:

public class Singleton
{
    private Singleton()
    {
    }

    public void doSomeThing()
    {
        Console.WriteLine("doSomeThing 测试方法执行");
    }
}

工厂类:

using System;
using System.Reflection;

public class SingletonFactory
{
    private static Singleton singleton;
    static SingletonFactory()
    {
        try
        {
            Type type = typeof(Singleton);

            System.Reflection.ConstructorInfo[] constructorInfoArray = type.GetConstructors(System.Reflection.BindingFlags.Instance
                        | System.Reflection.BindingFlags.NonPublic
                        | System.Reflection.BindingFlags.Public);
            System.Reflection.ConstructorInfo noParameterConstructorInfo = null;

            foreach (System.Reflection.ConstructorInfo constructorInfo in constructorInfoArray)
            {
                System.Reflection.ParameterInfo[] parameterInfoArray = constructorInfo.GetParameters();
                if (0 == parameterInfoArray.Length)
                {
                    noParameterConstructorInfo = constructorInfo;
                    break;
                }
            }

            if (null == noParameterConstructorInfo)
            {
                throw new NotSupportedException("No constructor without 0 parameter");
            }
            singleton = (Singleton)noParameterConstructorInfo.Invoke(null);
        }
        catch (Exception e)
        {
            Console.WriteLine("e=" + e.ToString());
        }
    }

    public static Singleton getSingleton()
    {
        return singleton;
    }
}

最后要想取用单例的话直接访问SingletonFactory.getSingleton()就可以了。

延迟初始化,或者说用来缓存多个对象

如下demo,ConcreteProduct1和ConcreteProduct2都继承自Product,其中如下ConcreteProduct2可以得知当前有几个对象来测试

public class ConcreteProduct2 : Product
{
    static int index = 0;
    public ConcreteProduct2()
    {
        index++;
    }

    public override void method2()
    {
        Console.WriteLine("ConcreteProduct2 的method2 index=" + index);
    }
}

用来缓存的工厂类:

public class ProductFactoryDelay
{
    private static Dictionary<string, Product> prMap = new Dictionary<string, Product>();
    public static Product createProduct(string type)
    {
        Product product = null;
        if (prMap.ContainsKey(type))
        {
            product = prMap[type];
        }
        else
        {
            if (type.Equals("ConcreteProduct1"))
            {
                product = new ConcreteProduct1();
            }
            else
            {
                product = new ConcreteProduct2();
            }
            prMap.Add(type, product);
        }

        return product;
    }
}

测试使用:

  private static void FactoryCacheTest()
        {
            Product p1 = ProductFactoryDelay.createProduct(typeof(ConcreteProduct1).Name);
            Product p2 = ProductFactoryDelay.createProduct(typeof(ConcreteProduct2).Name);
            p1.method2();
            p2.method2();
            Product p3 = ProductFactoryDelay.createProduct(typeof(ConcreteProduct2).Name);
            p3.method2();
        } 

最后根据打印结果发现 p2.method2()和 p3.method2()执行相同的内容,说明p2,p3取的是同一个对象,在某些场景重要保证实例对象的时候这个方法比较实用

抽象工厂模式(Abstract Factory Pattern)

定义:为创建一组相关/或相互依赖的对象提供一个接口,而无需指定它们的具体类。抽象工厂是工厂方法的升级版,

而抽象工厂类用来定义/声明每个子工厂的功能

简单说就是给有共同特性的几个工厂类加一个父类!

优点:
  • 封装性,工厂基类只需关心抽象和接口,不用关心工厂具体的实现;

- 产品实现类的约束非公开,即高层工厂不需要知道具体工厂内的

使用场景:

- 一组具有相同约束的对象,例如文本编辑器,在不同的操作系统里虽然显示和使用相同,但实现上不相同,可以通过抽象工厂来实现各个操作系统下的文本编辑器

模板方法模式

定义:定义一个操作中的算法的框架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重定义算法的某些特定步骤。

注意:抽象模板里的基本方法尽量用protected类型, 不需要暴露的部分privte。实现类非必要的话, 尽量不要扩大父类的访问权限。

优缺点:

优点:
- 封装不变部分,扩展可变部分:不变的算法部分放到父类
- 提取部分公共代码,便于维护
- 行为由父类控制,子类实现
缺点:
子类实现的执行结果影响父类的结果,(继承???),在复杂项目中

使用场景:

1.多个子类有公有方法,并且逻辑基本相同时
2.重要、复杂算法时 可以把核心算法设计为模板方法,细节由子类来实现
3.重构的生活,把相同代码提取到父类,通过钩子函数约束子类行为。

什么是钩子函数(Hook Method)?
比如抽象类中一个函数的返回值会影响到模板方法的执行结果(preturn true or false 有不同的响应),这个方法就叫钩子方法。

建造者模式(Builder Pattern)

定义:建造者模式也叫生成器模式,即将一个复杂对象的构建和它的表示分离开,使得同样的建造过程可以创建不同的表示。

建造者模式中的四个角色:

Product产品类:通常是实现了模板方法模式,也就是有模板方法和基本方法
Builder抽象建造者:规范产品组建,一般由子类实现
ConcreteBuilder具体建造者:实现抽象类定义的所有方法,并且返回一个组件好的对象,有几个产品类就有几个具体建造者
Director导演类:负责安排已有模块的顺序,告诉抽象建造者去建造产品
模式应用:
优缺点:

1.封装性:客户端不必知道产品内部细节,不用关心模型内部具体实现
2.建造者相互独立,荣誉扩展
3.便于控制细节风险,由于建造者独立,

使用场景:

1.相同的方法,不同的执行顺序要产生不同的事件结果时
2.多个部件或零件可以装配到一个产品中,但产生运行结果又不相同时
3.产品类非常复杂,或产品类中的调用顺序不同产生不同的效能就很适合用
4.创建对象过程中用到了系统中一些其他对象,而这些对象有不易获得时,就可以使用建造者模式封装这个过程

跟工厂模式的区别:更加关心零件类型和装配工艺,而工厂方式更注重创建

Java的实践demo:

package DesignPattern.BuilderPattern;
//抽象建造者
public abstract class Builder {

    //设置产品不同部分,以获得不同的产品
    public abstract void setPart();

    //构建产品
    public abstract Product buildProduct();
}

产品创建类

package DesignPattern.BuilderPattern;

public class ConcreteProduct extends Builder {

    private Product product= new  Product();
    @Override
    public void setPart() {
        // TODO Auto-generated method stub
        //产品类内的逻辑处理
    }

    @Override
    public Product buildProduct() {
        // TODO Auto-generated method stub
        return product;
    }

}

导演类

package DesignPattern.BuilderPattern;

//导演类
public class Director {

    private Builder builder = new ConcreteProduct();

    public Product getAProduct()
    {
        builder.setPart();
        return builder.buildProduct();
    }   
}

产品类

package DesignPattern.BuilderPattern;
//产品类
public class Product {

    public void doSomething()
    {
        //独立业务处理
        System.out.print("Product doSomething");
    }
}

使用调用

import java.io.*;
import DesignPattern.BuilderPattern.*;

public class JavaLanguageStudy {

    //yanruTODO程序入口
    public static void main( String args[])
    {
        //System.out.print("Hello java");

        //建造者模式
        Director dir = new Director();
        dir.getAProduct().doSomething();
    }
}

代理模式(Proxy Pattern)

定义:又叫委托模式,为其他对象提供一种代理以提控制对这个对象的访问,提供更好的访问控制。
包含的三个主题:

1.抽象主题角色:抽象类或接口
2.具体主题角色:被委托的角色
3.代理主题角色:负责对真实角色的应用

扩展部分:
虚拟代理:在需要的地方才去初始化被代理对象,避免大量初始化带来的额外消耗
动态代理:没太看懂demo???涉及到面向切面编程概念
面向切面(AOP)需要搞懂的东西:切面,切入点,通知,织入,yanruTODO待查询

原型模式(Prototype Pattern)

定义: 不通过new而通过复制来产生新对象的模式叫原型模式。用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象
模式应用:

(我突然开始强迫自己写英文了,由于写中文的markdown不知为什么卡的要死)

advantange:

1.copy binary stram and performance is higher than new a object.
2.constructor won’t be executed and this is a disadvantage

usage scenario:

1.if init a class needs much resource,data or hardware.

2.if new object frequently access data.

3.if some other object type needs to modify current object

4.this pattern is used frequently in Java,cause Java Object Class has itself clone(),I don’t know how’s it work in C#

深拷贝和浅拷贝(shallow copy and deep copy)的概念:
浅拷贝:Java的Object类提供的clone()只拷贝本对象,对其内部数组引用对象都不拷贝,还是指向原对象内存地址,这种拷贝就叫浅拷贝。

(需要注意Java中想使用clone()方法的话成员变量就不要用final关键字)
此外,在C#中,没有默认拷贝方法,对象赋值

TestClass b = new TestClass(); 
TestClass a = new TestClass(); 
a = b;

结果将导致a依然是指向b的引用,并不会复制出一个新的b,这种情况下,想要真正的复制一个a,只能像Java重写复制那样每个对象成员都单独赋值。

深拷贝:你拷贝你的,我拷贝我的,两个对象之间修改没有相互影响到,有点像拷贝值而不是引用,浅拷贝的复制对象其实依然指向原引用。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值