【学习笔记】设计模式六大原则之单一职责原则、里氏替换原则和迪米特法则

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

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

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

一、单一职责原则(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、去掉内部依赖,降低访问修饰符权限。依赖别人更少,让别人了解更少(只有必要的才公开)。

 

posted @ 2020-04-15 23:44  谢友海  阅读(664)  评论(0编辑  收藏  举报