• 浅谈《设计模式》


    设计模式,是一个程序员稍微进阶点的问题了。

    之前在开发的时候用过不少方法,包括Instance单例模式(最常见的就是游戏中的各种全局管理器等),以及订阅-发布(又称观察者模式)等,

    当时用的时候纯粹是觉得用这些方法能够优雅些,能够使代码整洁、规整下。也没想过这些事什么,

    直到最近无意看到一本书《大话设计模式》,才意识到编程中的设计问题。

    原来编程不只是能写出代码,能写出功能,如何把代码写得更加优雅,更加健硕,能减低各种耦合,能考虑后期维护成本,才是王道。

    我认真地读了下这本书,写一些笔记,算是心得体会吧。

    (注:学习设计模式首推《深入浅出设计模式》,一本外国人写的很有趣的书,《大话设计模式》是国人模仿那本书写的,总体还算不错,

    但我发现有一些问题,书里有些例子举得有些牵强,感觉是为了举例而举例,比如第一章,在设计计算器那块,明明可以用委托实现的简单功能,

    作者偏偏要举工厂模式的例子,不知道是我领悟不够还是怎么的,反正我觉得举错例子还不如不举例子)

    先来个UML类图说明吧,介绍模式的时候偶尔会用到:

    一、简单工厂模式

    简单工厂模式是属于创建型模式,又叫做静态工厂方法(Static Factory Method)模式,但不属于23种GOF设计模式之一。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单实用的模式,可以理解为是不同工厂模式的一个特殊实现。(我懒,直接复制百度百科)

    就好比,你在游戏里需要为主角创建衣服,裤子,鞋子,帽子等物品,但你总不能为每一种物品的创建都单独写一种实现吧,

    player.shoe = new Shoe();
    player.trousers = new Trousers();
    player.clothes = new Clother();

    这种写法明显有问题,具体是什么问题,我TM全忘了了。。==,只是觉得这样写很不好看。

    正确的做法是:

    1.将所有要创建的对象抽象出一个共同的父类,例如这里:

    class Equip{},里面描述所有子类的相同信息

    2.所有要生成的物品继承Equip,在工厂类中为每种物品实现生产的具体函数,例如:CreateShoe()

    工厂类的大概实现如下:

    class Factor{
    
    public static Equip Create(EquipType type){
      switch(type){
        case Shop:
          return CreateShoe();
          break;
        case Clother:
        ...
        }   }
    Equip createShoe(){}
    ... }

    3.创建物品时,直接通过工厂向工厂类中传入参数来指定生成哪一种产品:

    player.shoe = (Shoe)Factory.Create(EquipType.shop);
    player.clothes = (Clothes )Factory.Create(EquipType.clothes );

    最后,我们分析下该模式的优缺点:

    优点
    工厂类是整个模式的关键.包含了必要的逻辑判断,根据外界给定的信息,决定究竟应该创建哪个具体类的对象.通过使用工厂类,外界可以从直接创建具体产品对象的尴尬局面摆脱出来,仅仅需要负责“消费”对象就可以了。而不必管这些对象究竟如何创建及如何组织的.明确了各自的职责和权利,有利于整个软件体系结构的优化。
    缺点
    由于工厂类集中了所有实例的创建逻辑,违反了高内聚责任分配原则,将全部创建逻辑集中到了一个工厂类中;它所能创建的类只能是事先考虑到的,如果需要添加新的类,则就需要改变工厂类了。
    当系统中的具体产品类不断增多时候,可能会出现要求工厂类根据不同条件创建不同实例的需求.这种对条件的判断和对具体产品类型的判断交错在一起,很难避免模块功能的蔓延,对系统的维护和扩展非常不利;
    这些缺点在工厂方法模式中得到了一定的克服。

    二、单例模式

    单例模式是我最早接触的模式了,也是比较简单而且很实用的模式。
    单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例类的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。
    总的来说,单例模式有三个要点:
    1.一个类只有一个实例
    2.对外界提供访问接口
    3.它必须自动创建这个类的实例
    由此三点,我们可以简单地描述下单例的设计方法:
    1.将单例类的构造函数隐藏,防止被实例化
    2.提供一个public的静态函数,用于访问该单例
    3.在访问该单例的函数中判断该单例是否已经实例化,如果没,则实例化自身一次
    简单的代码实现如下:
    // 懒汉式
    public class SingletonClass{
        private static SingletonClass instance=null;
        public static SingletonClass getInstance()
        {
            if(instance==null)
            {
                   instance=new SingletonClass();
            }
            return instance;
        }
        private SingletonClass(){
        }
    }

    但以上有些问题,就是在多线程下易出错误,比如两个线程同时调用该单例,而且该单例还未实例化,

    那么就会出现以下问题,线程1访问时,由于instance == null,所有线程一会实例化该单例,而线程2访问时,

    instance也有可能为null,所以他也实例化一次,这就会出现问题,解封方法就是用双向锁:

    // 双从锁定
    class Singleton{
        private static Singleton instance=null;
        private static readonly object syncRoot = new object();
        private Singleton(){
            //do something
        }
        public static Singleton getInstance(){
            if(instance==null){
                lock(syncRoot){
                    if(null==instance){
                        instance=new Singleton();
                    }
                }
            }
            return instance;
        }
    }

    如果是C#语言的话,还可以用静态初始化的方法,那样更简洁:

    // C#静态初始化方法(即饿汉式方法)
    public sealed class Singleton
    {
        private static readonly Singleton instance = new Singleton();
        private Singleton() {}
        public static Singleton GetInstanceJ()
        {
            return instance;
        }
    }

    扩展: Unity3D实现继承于MonoBehaviour的单例

    由于MonoBehaviour的特殊性(不能new,自动消耗,自动调用周期函数),所以实现起来稍微有点不一样:

    要点:

    a.其他部分跟普通单例差不多,新增的Awake里的代码,是由于U3D有AddCompent的特性,避免单例组件被多次Add导致重复生成单例对象。

    b.DontDestoryOnLoad是由于U3D的所有对象都在切换场景时释放,为了避免单例对象被释放,应该加这一句。

    还可以将上面的实现抽成抽象,做成一套模板,这样就可以省事很多,毕竟一个游戏里可能有会很多单例管理器:

     

    至于泛型的理解,参考我这篇文章中的第八条。

    注:该模版只是将单例的通用创建以及处理方式抽象出来而已,被写死在模板里,如果要利用此模板实现特定的单例,

    必须得用继承,例如:class MyUIManager : Singleton<UIManager>,然后使用的时候就MyUIManager.Instacne即可。

    三、依赖导致原则

    一篇非常好的文章

  • 相关阅读:
    ECSHOP 2.5.1 二次开发文档【文件结构说明和数据库表分析】
    ECSHOP后台左侧添加菜单栏
    PHPstudy 2018 集成环境项目配置虚拟域名访问
    微擎左侧模块业务菜单修改
    ThinkPHP5使用阿里云OSS图片上传
    ThinkPHP5使用PHPExcel实现数据导出功能
    ThinkPHP5生成二维码图片与另一张背景图片进行合成
    原生PHP连接MySQL数据库
    数组的冒泡排序
    【原创诗歌】读仓央嘉措(下)
  • 原文地址:https://www.cnblogs.com/jeason1997/p/4950175.html
Copyright © 2020-2023  润新知