C#基础笔记(4)—— C# 设计模式

C 设计模式 一、设计模式 设计模式(Design pattern):是帮助我们解决实际开发过程中的方法,该方法是为了降低对象之间的耦合度。 设计模式的意义:

C#设计模式

一、设计模式

设计模式(Design pattern):是帮助我们解决实际开发过程中的方法,该方法是为了降低对象之间的耦合度

设计模式的意义:

​ 使用设计模式是为了可重用代码,让代码更容易被他人理解、保证代码可靠性。设计模式使代码编制真正工程化,设计模式就好比是软件工程的基石脉络。

设计模式分类:
  • 创建型模式(5种)
    • 工厂方法模式
    • 抽象工厂模式
    • 单例模式
    • 建造者模式
    • 原型模式
  • 结构型模式(7种)
    • 适配器模式
    • 装饰器模式
    • 代理模式
    • 外观模式
    • 桥接模式
    • 组合模式
    • 享元模式
  • 行为型模式(11种)
    • 策略模式
    • 模板方法模式
    • 观察者模式
    • 迭代子模式
    • 责任链模式
    • 命令模式
    • 备忘录模式
    • 状态模式
    • 访问者模式
    • 中介者模式
    • 解释器模式

二、单例模式

单例模式,即所谓的一个类只能有一个实例,也就是类只能在内部实例化一次,然后提供这一实例,外部无法对此类实例化。

单例模式的特点:
  1. 只能有一个实例;
  2. 只能自己创建自己的唯一实例;
  3. 全局唯一。
单例模式的实现:
/**
 *  单例:
 *    1. 实例全局唯一
 *       使用static(属于全局,并不属于类对象本身)实现
 *    2. 实例化只能在类的内部发生
 *       需要将构造函数私有化
 *    3. 需要提供一个供外部访问的变量
 */
// 单例的实现
public class MySingleton
{
    private static MySingleton _instance;
    public static MySingleton Instance
    {
        get
        {
            if(_instance == null)
            {
                _instance = new MySingleton();
            }
            return _instance;
        }
    }

    private MySingleton()
    {
        Debug.Log("单例模式");
    }

    public void Show()
    {
        Debug.Log("Show");
    }
}

public class Simple
{
    public Simple()
    {
        Debug.Log("构造函数");
    }
}
public class Singleton : MonoBehaviour
{
    void Start()
    {
        Simple single_1 = new Simple();
        Simple single_2 = new Simple();
        Simple single_3 = new Simple();
        // 三个不同实例,哈希码不同
        Debug.Log("single_1 = " + single_1.GetHashCode()
            + ", single_2 = " + single_2.GetHashCode()
            + ", single_3 = " + single_3.GetHashCode());
        
        // 采用单例模式
        MySingleton single1 = MySingleton.Instance;
        single1.Show();

        MySingleton single2 = MySingleton.Instance;
        single2.Show();

        MySingleton single3 = MySingleton.Instance;
        single3.Show();

        Debug.Log("single1 = " + single1.GetHashCode()
            + ", single2 = " + single2.GetHashCode()
            + ", single3 = " + single3.GetHashCode()); // 哈希码相同证明是同一对象,是单例模式
    }
}

三、观察者模式

观察者模式:

​ 又称 发布/订阅模式,观察者模式定义了一种 一对多 的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。

(该模式常被用来实现事件处理系统。)

观察者模式的特点:

​ 发布者 + 订阅者 = 观察者模式。

观察者模式的设计原理:
  • 先设置一个主题(Subject)

  • 让这个主题发布时可同时通知关心这个主题的观察者/订阅者

  • 并且主题不必理会观察者/订阅者接下来会执行哪些操作

观察者模式的主要功能和优点:

​ 将“主题发生”与“功能执行”这两个操作解除绑定

观察者模式的应用:
  • C#中的事件系统(Event)便是使用的观察者模式

  • Unity中的所有事件(如UI点击事件)也是应用的观察者模式

观察者模式示例:
/**
 *  观察者模式
 *   需求:猫来了,老鼠A, B, C惊吓逃走
 */
public class Animal
{
    protected string Name;
    public Animal(string name)
    {
        this.Name = name;
    }

    public virtual void Run(){ }
}

public class Cat : Animal
{
    public Action actions; // 发布者
    public Cat(string name) : base(name) { }

    public void Coming()
    {
        Debug.Log( Name + "来了");
        if(actions != null)
        {
            actions();
        }
        this.Run();

    }
    public override void Run()
    {
        Debug.Log(Name + "追老鼠");
    }
}

public class Mouse : Animal
{
    public Mouse(string name, Cat cat) : base(name) 
    {
        cat.actions += this.Run; // 订阅者
    }

    public override void Run()
    {
        Debug.Log(Name + "逃跑");
    }
}

public class Observer : MonoBehaviour
{
    void Start()
    {
        Cat cat = new Cat("猫");

        Animal mousea = new Mouse("老鼠A", cat);
        Animal mouseb = new Mouse("老鼠B", cat);
        Animal mousec = new Mouse("老鼠C", cat);
        Animal moused = new Mouse("老鼠D", cat);

        cat.Coming();
    }
}

四、简单工厂模式

简单工厂模式:

  • 属于创建型模式,又叫做静态工厂方法(Static Factory Method)模式。
  • 简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。
  • 简单工厂模式是工厂模式家族中最简单实用的模式,可以理解为是不同工厂模式的一个特殊实现。

特点:只生产一种品牌(类型)的产品,在工厂中动态创建。

简单工厂的优点:
  1. 简单工厂可以有效地降低客户端和具体对象的耦合,将new具体对象的任务交给了一个简单工厂类
  2. 可以有效的进行代码复用,如客户端A和客户端B都需要一个具体对象,客户端A和客户端B可以通过同一个简单工厂来获取具体类型的实例
简单工厂的缺点:

  一定程度上违背了开闭原则,在新增产品时需要修改简单工厂类

简单工厂模式示例:
// 假设鼠标有两种:戴尔鼠标和惠普鼠标
//鼠标抽象类
public abstract class Mouse
{
    public abstract void Print();
}


//戴尔鼠标
public class DellMouse : Mouse
{
    public override void Print()
    {
        Console.WriteLine("生产了一个Dell鼠标!");
    }
}

//惠普鼠标
public class HpMouse : Mouse
{
    public override void Print()
    {
        Console.WriteLine("生产了一个惠普鼠标!");
    }
}

/// <summary>
/// 鼠标工厂类
/// </summary>
public class MouseFactory
{
    //从配置文件中读取品牌
    private static readonly string brand = ConfigurationManager.AppSettings["brand"];
    private Mouse mouse = null;
    public Mouse CreateMouse()
    {
        switch (brand)
        {
            case "dell":
                mouse = new DellMouse();
                break;
            case "hp":
                mouse = new HpMouse();
                break;
            default:
                break;
        }
        return mouse;
    }
}
// 客户端
class Program
{
    static void Main(string[] args)
    {
        //实例化一个工厂类
        MouseFactory mouseFactory = new MouseFactory();
        //通过工厂类创建鼠标
        Mouse mouse1 = mouseFactory.CreateMouse();
        Mouse mouse2 = mouseFactory.CreateMouse();
        Mouse mouse3 = mouseFactory.CreateMouse();
        Mouse mouse4 = mouseFactory.CreateMouse();
        Mouse mouse5 = mouseFactory.CreateMouse();
        mouse1.Print();
        Console.ReadKey();
    }
}
// 有效降低了客户端与DellMouse间的耦合
// 配置文件
<appSettings>
    <add key="dbname" value="dell"/>
</appSettings>

五、工厂模式

工厂模式:

  • 避免简单工厂模式中,新增产品品牌(类型)时,直接修改工厂类。为了解决这个问题出现了 工厂模式。
  • 解决的方法是把创建具体实例的任务放在了工厂的子类中,工厂只提供了创建实例的的接口

特点:只生产一种品牌(类型)的产品,在具体的子类工厂中创建。为了解决系列产品的问题,就有了抽象工厂模式。

工厂模式的优点:

  工厂模式有效地解决了添加新产品必须要修改工厂类代码的问题,符合设计原则中的开闭原则。

工厂模式的缺点:

  工厂模式的本质是将具体实例的创建工作放在了具体子类工厂中进行,这造成一个新的问题:将选择实例类型的任务交给了客户端

工厂模式示例:
//鼠标抽象类
public abstract class Mouse
{
    public abstract void Print();
}

//戴尔鼠标
public class DellMouse : Mouse
{
    public override void Print()
    {
        Console.WriteLine("生产了一个Dell鼠标!");
    }
}

//惠普鼠标
public class HpMouse : Mouse
{
    public override void Print()
    {
        Console.WriteLine("生产了一个惠普鼠标!");
    }
}

/*
   工厂类只提供生产鼠标的抽象方法(或者接口),其子类生产具体的产品,如:
   戴尔鼠标工厂继承于鼠标工厂,它只生产戴尔鼠标;
   惠普鼠标工厂只生产惠普鼠标
 */
/// <summary>
/// 鼠标工厂抽象类
/// </summary>
public abstract class MouseFactory
{
    public abstract Mouse CreateMouse();
}

//戴尔鼠标工厂
public class DellMouseFactroy : MouseFactory
{
    public override Mouse CreateMouse()
    {
        return new DellMouse();//在具体的工厂中实例化产品
    }
}

//惠普鼠标工厂
public class HpMouseFactory : MouseFactory
{
    public override Mouse CreateMouse()
    {
        return new HpMouse();//在具体的工厂中实例化产品
    }
}
// 客户端
static void Main(string[] args)
{
    //生产一个戴尔鼠标
    MouseFactory dellMouseFactory = new DellMouseFactroy();
    Mouse dellMouse= dellMouseFactory.CreateMouse();
    dellMouse.Print();

    //生产一个惠普鼠标
    MouseFactory hpMouseFactory = new HpMouseFactory();
    Mouse hpMouse = hpMouseFactory.CreateMouse();
    hpMouse.Print();
    Console.ReadKey();
}

六、抽象工厂模式

抽象工厂模式:

  • 为了解决系列产品的问题,就有了抽象工厂模式。

特点:抽象工厂可以生产多种产品。而简单工厂、工厂模式只能生产一种产品。即只有一种产品的抽象工厂就是工厂模式。

抽象工厂模式的优点

​ 抽象工厂具有工厂模式的优点,对添加系列产品符合闭合原则(工厂模式的系列产品只有一种)。

抽象工厂模式的缺点

​ 抽象模式对添加新产品不符合开闭原则。

抽象工厂模式示例:
// 产品类
//鼠标抽象类
public abstract class Mouse
{
    public abstract void Print();
}

//戴尔鼠标
public class DellMouse : Mouse
{
    public override void Print()
    {
        Console.WriteLine("生产了一个Dell鼠标!");
    }
}

//惠普鼠标
public class HpMouse : Mouse
{
    public override void Print()
    {
        Console.WriteLine("生产了一个惠普鼠标!");
    }
}


//键盘抽象类
public abstract class Keybo
{
    public abstract void Print();
}

//戴尔键盘类
public class DellKeybo : Keybo
{
    public override void Print()
    {
        Console.WriteLine("生产了一个戴尔键盘!");
    }
}

//惠普键盘
public class HpKeybo : Keybo
{
    public override void Print()
    {
        Console.WriteLine("生产了一个惠普键盘!");
    }
}

/// <summary>
/// Pc产品工厂抽象类
/// </summary>
public abstract class PcFactory
{
    public abstract Mouse CreateMouse();
    public abstract Keybo CreateKeybo();
}

//戴尔pc工厂
public class DellPcFactroy : PcFactory
{
    public override Keybo CreateKeybo()
    {
        return new DellKeybo();
    }

    public override Mouse CreateMouse()
    {
        return new DellMouse();
    }
}

//惠普pc工厂
public class HpPcFactory : PcFactory
{
    public override Mouse CreateMouse()
    {
        return new HpMouse();
    }
    public override Keybo CreateKeybo()
    {
        return new HpKeybo();
    }

}

// 客户端
class Program
{
    static void Main(string[] args)
    {
        //生产一个戴尔鼠标/键盘
        PcFactory dellFactory = new DellPcFactroy();
        Mouse dellMouse= dellFactory.CreateMouse();
        Keybo dellKeybo = dellFactory.CreateKeybo();
        dellMouse.Print();
        dellKeybo.Print();

        //生产一个惠普鼠标/键盘
        PcFactory hpFactory = new HpPcFactory();
        Mouse hpMouse = hpFactory.CreateMouse();
        Keybo hpKeybo = hpFactory.CreateKeybo();
        hpMouse.Print();
        hpKeybo.Print();
        Console.ReadKey();
    }
}

七、适配器模式

适配器模式的作用:

将一个类的接口,转换成客户端希望的另一种接口。适配器作为原始接口(类中本来具有的功能)和目标接口(客户端希望的功能)之间的桥梁。

适配器模式的类型:

类适配器,通过多重继承实现接口的匹配,C#不支持多重继承,故不考虑。

对象适配器,适配器模式的三个角色:

  • Adaptee,初始角色,实现了我们想要的功能,但接口不匹配;
  • Target,目标角色,定义了客户端期望的接口;
    • Adapter,适配器角色,实现了目标接口。实现目标接口的方法是:内部包含一个Adaptee的对象,通过这个对象调用Adaptee的原有方法实现目标接口。
适配器模式示例:
/// <summary>
/// 安卓数据线,adaptee角色
/// </summary>
public class AndroidLine
{
    public void AndroidCharge()
    {
        Console.WriteLine("安卓数据线充电....");
    }
}

/// <summary>
/// 苹果手机充电接口,目标接口
/// </summary>
public interface IApplecharge
{
    void AppleCharge();
}

/// <summary>
/// 苹果适配器 适配器角色
/// </summary>
public class AppleAdapter : IAppleCharge
{
    AndroidLine androidLine = new AndroidLine();//适配器内部包含一个Adaptee对象
    public void AppleCharge()
    {
        androidLine.AndroidCharge(); //客户端调用时,表面上是用的是AppleCharge方法,本质还是用的AndroidCharge方法
    }
}
// 客户端调用
class Program
{
    static void Main(string[] args)
    {
        //获取一个target的实例
        IApplyCharge applyAdapter = new ApplyAdapter();
        applyAdapter.ApplyChange();//表面上用的苹果充电方法ApplyChange,本质上还是用的安卓充电方法AndriodChange。

        Console.ReadKey();
    }
}
适配器的使用场景:

  系统想使用一个类,但是这个类的接口不符合系统的要求时使用适配器模式。

适配器模式的优点:

  1. 提高代码复用,复用了Adaptee中的方法

  2. 灵活性好,如没有关联的两个类A和类B,类A想借用类B的方法,可以在类A中添加一个类B的实例,然后调用类B实例的方法即可。

适配器模式的缺点:
  • 过多使用适配器会让功能凌乱,如我们明明调用的是A接口的功能,但是却被适配成了B接口的功能。