zoukankan      html  css  js  c++  java
  • 【学习笔记】设计模式六大原则之单一职责原则、里氏替换原则和迪米特法则

    进入主题前我们来看下什么是设计模式

    设计模式:面向对象语言开发过程中,遇到各种各样的场景和问题,提出的解决方案和思路,沉淀下来。设计模式是解决具体问题的套路。

    设计模式六大原则:面向对象语言开发过程中,推荐的一些指导性原则。没有明确的招数,而且也经常会被忽视/违背。

    一、单一职责原则(Single Responsibility Principle)

    首先我们来看个示例:

    /// <summary>
    /// 动物类
    /// </summary>
    public class Animal
    {
        private string _name = null;
        public Animal(string name)
        {
            this._name = name;
        }
    
        /// <summary>
        /// 呼吸
        /// </summary>
        public void Breath()  //这个方法就很不稳定,只要分支变化就会引起修改
        {
            if (this._name.Equals(""))
                Console.WriteLine($"{this._name} 呼吸空气");
            else if (this._name.Equals(""))
                Console.WriteLine($"{this._name} 呼吸空气");
            else if (this._name.Equals(""))
                Console.WriteLine($"{this._name} 呼吸水");
            else if (this._name.Equals("蚯蚓"))
                Console.WriteLine($"{this._name} 呼吸泥土");
        }
    
        /// <summary>
        /// 运动
        /// </summary>
        public void Action() //多个方法时就应该考虑拆分了
        {
            if (this._name.Equals(""))
                Console.WriteLine($"{this._name} flying");
            else if (this._name.Equals(""))
                Console.WriteLine($"{this._name} walking");
            else if (this._name.Equals(""))
                Console.WriteLine($"{this._name} Swimming");
            else if (this._name.Equals("蚯蚓"))
                Console.WriteLine($"{this._name} Crawling");
        }
    }

    从上面的例子中可以看出每种动物都有自己的呼吸和运行方式,如果都写在动物一个类里面,则很不稳定,只要一个分支发生改变就要修改方法。这就违背了单一职责原则。

    竟然每种动物都有自己的呼吸和运行方式,那我们能不能根据动物的种类来拆分Animal类呢,拆分后使每种动物都职责单一。下面来看下如何拆分。

    /// <summary>
    /// 动物抽象类
    /// </summary>
    public abstract class AbstractAnimal
    {
        protected string _name = null;
        public AbstractAnimal(string name)
        {
            this._name = name;
        }
    
        public abstract void Breath();
        public abstract void Action();
    }
    
    /// <summary>
    ////// </summary>
    public class Chicken : AbstractAnimal
    {
        public Chicken() 
            : base("")
        {
        }
    
        public override void Breath()
        {
            Console.WriteLine($"{base._name} 呼吸空气");
        }
    
        public override void Action()
        {
            Console.WriteLine($"{base._name} flying");
        }
    }
    
    /// <summary>
    ////// </summary>
    public class Fish : AbstractAnimal
    {
        public Fish()
            : base("")
        {
        }
    
        public override void Breath()
        {
            Console.WriteLine($"{base._name} 呼吸水");
        }
    
        public override void Action()
        {
            Console.WriteLine($"{base._name} swimming");
        }
    }

    经过这样拆分后我们的类简单了,职责也就单一了,简单意味着稳定。当然拆分后也会造成代码量的增加,类多了,使用成本也高(理解成本)。

    那么我们究竟该什么时候使用单一职责原则呢?

    如果类型足够简单,方法够少,是可以在类级别去违背单一职责。如果类型复杂了,方法多了,建议遵循单一职责原则。

    小结:

    1、单一职责原则:类T负责两个不同的职责:职责P1,职责P2。当由于职责P1需求发生改变而需要修改类T时,有可能会导致原本运行正常的职责P2功能发生故障。

    2、一个类只负责一件事,面向对象语言开发,类是一个基本单位,单一职责原则就是封装的粒度。

    3、写分支判断,然后执行不同的逻辑,其实这就违背了单一职责原则,但是功能是可以实现的。

    4、方法级别的单一职责原则:一个方法只负责一件事(职责分拆小方法,分支逻辑分拆)。

    5、类级别的单一职责原则:一个类只负责一件事。

    6、类库级别的单一职责原则:一个类库应该职责清晰。

    7、项目级别的单一职责原则:一个项目应该职责清晰(客户端/管理后台/后台服务/定时任务/分布式引擎)。

    8、系统级别的单一职责原则:为通用功能拆分系统(IP定位/日志/在线统计)。

    二、里氏替换原则(Liskov Substitution Principle)

    下面我们来看个示例:

    public class People
    {
        public int Id { get; set; }
        public string Name { get; set; }
    
        //传统
        public void Traditional()
        {
            Console.WriteLine("仁义礼智信 温良恭俭让 ");
        }
    }
    
    public class Chinese : People
    {
        public string Kuaizi { get; set; }
        public void SayHi()
        {
            Console.WriteLine("早上好,吃了吗?");
        }
    }
    
    public class Hubei : Chinese
    {
        public string Majiang { get; set; }
        public new void SayHi()
        {
            Console.WriteLine("早上好,过早了么?");
        }
    }
    
    //这就违背了里氏替换原则,因为父类中出现了子类中没有的行为,那么就应该断掉继承。
    public class Japanese : People
    {
        //Traditional也会继承 但是Japanese又没有Traditional
        public void Ninja()
        {
            Console.WriteLine("忍者精神 ");
        }
    }

    从示例中可以看出Japanese类继承People类,但是基类People中有子类Japanese类没有的行为Traditional,这就违背了里氏替换原则,此时就应该断掉继承关系,可以考虑重新去创建一个公共的父类。

    小结:

    1、继承:子类拥有父类的一切属性和行为,任何父类出现的地方,都可以用子类来代替。

    2、里氏替换原则:任何使用基类的地方,都可以透明的使用其子类。继承+透明(透明:安全,不会出现行为不一致。)

    3、父类有的,子类是必须有的;如果父类出现了子类没有的东西,那么就应该断掉继承;再来一个父类,只包含都有的东西。

    4、子类可以有自己的属性和行为,子类出现的地方父类不一定能代替。

    5、父类实现的东西,子类就不要再写了(就是不要new隐藏),因为有时候会出现意想不到的情况,把父类换成子类后,行为不一致。如果想修改父类的行为,通过abstract/virtual。

    6、声明属性、字段、变量,尽量声明为父类(父类就能满足)。

    三、迪米特法则(Law Of Demeter)

     迪米特法则(最少知道原则):一个对象应该对其他对象保持最少的了解。只与直接的朋友通信。

    示例1:

    /// <summary>
    /// 学生
    /// </summary>
    public class Student
    {
        public int Id { get; set; }
        public string StudentName { get; set; }
        public int Height { private get; set; }
    
        public int Salay;
    }
    
    /// <summary>
    /// 班级
    /// </summary>
    public class Class
    {
        public int Id { get; set; }
    
        public string ClassName { get; set; }
    
        public List<Student> StudentList { get; set; }
    }
    
    /// <summary>
    /// 学校
    /// </summary>
    public class School
    {
        public int Id { get; set; }
        public string SchoolName { get; set; }
        public List<Class> ClassList { get; set; } //直接的朋友
    
        public void Manage()
        {
            Console.WriteLine("Manage {0}", this.GetType().Name);
            foreach (Class c in this.ClassList)
            {
                Console.WriteLine(" {0}Manage {1} ", c.GetType().Name, c.ClassName); //管理班级
                List<Student> studentList = c.StudentList; //违背了迪米特法则,出现了不知道的类型
                foreach (Student s in studentList)
                {
                    Console.WriteLine(" {0}Manage {1} ", s.GetType().Name, s.StudentName); //管理学生
                }
            }
        }
    }

    从示例1中可以看出School类的管理方法Manage中不仅出现了Class类(直接的朋友),而且也出现了Student类,此时就出现了依赖Student类,这就违背了迪米特法则。

    下面我们针对这个问题对示例1做一下小调整以遵循迪米特法则。

    示例2:

    /// <summary>
    /// 学生
    /// </summary>
    public class Student
    {
        public int Id { get; set; }
        public string StudentName { get; set; }
        public int Height { private get; set; }
    
        public int Salay;
    
        /// <summary>
        /// 管理学生自己
        /// </summary>
        public void ManageStudent()
        {
            Console.WriteLine(" {0}Manage {1} ", this.GetType().Name, this.StudentName);
        }
    }
    
    /// <summary>
    /// 班级
    /// </summary>
    public class Class
    {
        public int Id { get; set; }
    
        public string ClassName { get; set; }
    
        public List<Student> StudentList { get; set; }
    
        /// <summary>
        /// 管理班级自己
        /// </summary>
        public void ManageClass()
        {
            Console.WriteLine(" {0}Manage {1} ", this.GetType().Name, this.ClassName);
            foreach (Student s in this.StudentList)
            {
                s.ManageStudent();
                //Console.WriteLine(" {0}Manage {1} ", s.GetType().Name, s.StudentName);
            }
        }
    }
    
    /// <summary>
    /// 学校
    /// </summary>
    public class School
    {
        public int Id { get; set; }
        public string SchoolName { get; set; }
        public List<Class> ClassList { get; set; } //直接的朋友
    
        public void Manage()
        {
            Console.WriteLine("Manage {0}", this.GetType().Name);
            foreach (Class c in this.ClassList)
            {
                //1 遵循了迪米特法则
                c.ManageClass();
    
                //2 违背了迪米特法则
                //Console.WriteLine(" {0}Manage {1} ", c.GetType().Name, c.ClassName);
                //List<Student> studentList = c.StudentList; 
                //foreach (Student s in studentList)
                //{
                //    Console.WriteLine(" {0}Manage {1} ", s.GetType().Name, s.StudentName);
                //}
            }
        }
    }

    小结:

    1、迪米特法则(最少知道原则):一个对象应该对其他对象保持最少的了解。只与直接的朋友通信。

    2、类与类之间的关系

      纵向:继承≈实现(最密切)

      横向:聚合> 组合> 关联> 依赖(未知的类出现在方法内部)

    3、高内聚低耦合,迪米特法则,降低类与类之间的耦合,只与直接的朋友通信就是要尽量避免依赖更多类型。

    4、工作中为了更好的遵循迪米特法则,有时候会去造一个中介/中间层,例如:门面模式、中介者模式、分层封装等。

    5、去掉内部依赖,降低访问修饰符权限。依赖别人更少,让别人了解更少(只有必要的才公开)。

  • 相关阅读:
    安卓虚拟机启动后报错: 类似 SDK Manager] Error: Error parsing .....devices.xml 解决方案
    error when loading the sdk 发现了元素 d:skin 开头无效内容 转自http://blog.csdn.net/yueqinglkong/article/details/46340571
    mysql 5.5及以前版本的编码问题“Incorrect string value: 'xE6x9BxB9xE5x86xAC...' for column 'realname' at row 1”
    HDU 1024 Max Sum Plus Plus(基础dp)
    POJ 3026 --Borg Maze(bfs,最小生成树,英语题意题,卡格式)
    算法分类合集(转)
    ACM训练计划建议(转)
    The 15th Zhejiang Provincial Collegiate Programming Contest Sponsored by TuSimple
    The 15th Zhejiang Provincial Collegiate Programming Contest Sponsored by TuSimple
    iOS消息推送机制的实现
  • 原文地址:https://www.cnblogs.com/xyh9039/p/12708931.html
Copyright © 2011-2022 走看看