16设计模式六大原则

一设计模式六大原则包括:

单一职责原则、里氏替换原则、依赖倒置原则、接口隔离原则、迪米特法则、开闭原则。

1.单一职责原则

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

举个动物呼吸的例子:

先建一个Animal类:

public class Animal
{
    string _Name;

    public Animal(string name)
    {
        _Name = name;
    }

    public void Breath()
    {
        Console.WriteLine("{0} 呼吸空气", this._Name);
    }
}

实例化一个动物--“牛”,让它来呼吸:

public void Show()
{
    Animal animal = new Animal("牛");
    animal.Breath();
}

好的,没有问题,另外还可以实例化“羊”,“马”,“鸡”,都可以。但如果是“鱼”呢?(我们假设鱼是呼吸水的),就不行了。

当然我们可以用case语句,case "牛":“呼吸空气” ; case "鱼":"呼吸水",但这样不好,如果还有其他动物呢,那就要频繁修改Animal类了。

正确做法是,建一个BaseAnimal的抽象类:

public abstract class BaseAnimal
{
    protected string _Name = null;
    public BaseAnimal(string name)
    {
        this._Name = name;
    }
    public abstract void Breath();
}

再建陆生动物Animal类和水生动物AnimalWater类继承它:

public class Animal : BaseAnimal
{
    public Animal(string name): base(name)
    {
    }
    public override void Breath()
    {
        Console.WriteLine("{0} 呼吸空气", this._Name);
    }
}
public class AnimalWater : BaseAnimal
{
    public AnimalWater(string name): base(name)
    {
    }
    public override void Breath()
    {
        Console.WriteLine("{0} 呼吸水", this._Name);
    }
}

如果以后遇上”蚯蚓“(假设蚯蚓是呼吸泥土的),那么就新建一个地下动物的类。

2.里氏替换原则

所有引用基类的地方必须能透明地使用其子类的对象。

例子:

先写一个父类:

public abstract class ParentClass
{
    //普通方法
    public void CommonMethod()
    {
        Console.WriteLine("ParentClass CommonMethod");
    }

    /// <summary>
    /// virtual  虚方法  必须包含实现 但是可以被重载
    /// </summary>
    public virtual void VirtualMethod()
    {
        Console.WriteLine("ParentClass VirtualMethod");
    }

    //虚方法
    public virtual void VirtualMethod(string name)
    {
        Console.WriteLine("ParentClass VirtualMethod");
    }

    //抽象方法
    public abstract void AbstractMethod();
}

子类:

public class ChildClass : ParentClass
{
    // new 隐藏父类的普通方法
    public new void CommonMethod()
    {
        Console.WriteLine("ChildClass CommonMethod");
    }

    public void CommonMethod(string name)
    {
        Console.WriteLine("ChildClass CommonMethod");
    }
        
    public void CommonMethod(int id, string name = "", string des = "", int size = 0)
    {
        Console.WriteLine("ChildClass CommonMethod");
    }

    public void CommonMethod(string name, int id)
    {
        Console.WriteLine("ChildClass CommonMethod");
    }

    // virtual 覆写父类虚方法
    public override void VirtualMethod()
    {
        Console.WriteLine("ChildClass VirtualMethod");
        base.VirtualMethod();
    }

    // 复写父类抽象方法
    public sealed override void AbstractMethod()
    {
        Console.WriteLine("ChildClass AbstractMethod");
    }
}

调用:

ParentClass instance = new ChildClass();
instance.CommonMethod();       //普通方法,是调用父类的
instance.VirtualMethod();      //虚方法,是调用子类的
instance.AbstractMethod();     //抽象方法,是调用子类的

里氏替换原则和多态有些不一致的地方,例子:

父类(中国人):

public class Chinese
{
    public string Kuaizi { get; set; }
    public void SayHi()
    {
        Console.WriteLine("早上好,吃了吗?");
    }

    public virtual void SayHiVirtual()
    {
        Console.WriteLine("早上好,吃了吗?");
    }
}

子类(湖北人):

public class Hubei : Chinese
{
    public override void SayHiVirtual()
    {
        Console.WriteLine("早上好,吃了吗?");
    }

    public new void SayHi()
    {
        Console.WriteLine("早上好,过早了么?");
    }
}

调用:

{
    //代码1
    Chinese chinese = new Chinese();
    chinese.SayHi(); //调用的是父类的方法
}

{
    //代码2
    Chinese chinese = new Hubei();
    chinese.SayHi(); //同样调用的是父类的方法
}

{
    //代码3
    Hubei chinese = new Hubei();
    chinese.SayHi(); //调用的是子类的方法
}

按照上面普通方法的调用方式,代码1,代码2,都是调用父类的普通方法,但是代码3调用的是子类自己的普通方法。如果我们按照接口编程,面向抽象,代码1,代码2,都调用了父类的普通方法,如果有一天,代码扩展,换成了子类,那么调用的方法不是我们希望的。所以在子类中用new来隐藏父类已经存在的方法,是违背了里氏替换原则,父类已经实现的东西,子类就别修改了,但这个又和多态冲突了。所以子类在继承父类的时候要遵信两个原则:

1)继承是为了减少重复代码,那么子类就别再修改父类代码

2)继承是为了多态,父类用可以override 的 virtual、abstract的方法

同时,我们在做程序设计的时候,如果不希望别人修改,就写成普通方法,如果允许别人修改,就写成虚方法或者抽象方法让别人去复写。

3.迪米特法则

又叫做最少知道原则,一个对象应该对其他对象保持最少的了解,它只与直接的朋友通信。

它的目标是高内聚,低耦合。

例子:

一个学校由多个班级,一个班级由多个学生。学校只管理班级,不直接管理到学生,学生由班级去管理。

学校:

public class School
{
    public int Id { get; protected 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);
            //让班级去管理学生,而不是在学校这个类里面来管理学生,这个是迪米特法则
            c.Manage();
            
            //如果这样写,学校直接管理了学生,以后如果学生有变动,都要来修改学校这个类,违反了迪米        特法则
            List<Student> studentList = c.StudentList;
            foreach (Student s in studentList)
            {
                Console.WriteLine(" {0}Manager {1} ", s.GetType().Name, s.StudentName);
            }
        }
    }
}

班级:

public class Class
{
    public int Id { get; set; }
    public string ClassName { get; set; }

    public List<Student> StudentList { get; set; }

    //班级管理学生的方法
    public void Manage()
    {
        foreach (Student s in this.StudentList)
        {
            Console.WriteLine(" {0}Manage {1} ", s.GetType().Name, s.StudentName);
        }
    }
}

学生:

public class Student
{
    public int Id { get; set; }
    public string StudentName { get; set; }
}

比如在一个系统有多个子系统,而UI需要访问每个子系统,为了让业务访问更加简洁,在UI层和各个子系统中间新建一个中间层,让中间层去代替UI层去访问多个子业务,这样可以减少依赖、转移依赖。

用的最多的三层架构,就是迪米特法则,UI层只能访问业务层,不能直接调用数据层,只有业务层才能直接调用数据层。

4.依赖倒置原则

上层模块不应该依赖于下层模块,应该依赖于抽象。

举个手机的例子,手机有iphone,也有安卓,安卓又有努米亚,荣耀,小米等等

先建一个抽象类:

public abstract class AbstractPhone
{
    public int Id { get; set; }
    public string Branch { get; set; }
    //打电话
    public abstract void Call();
    //发短信
    public abstract void Text();
}

iphone类:

public class iPhone : AbstractPhone
{
    public override void Call()
    {
        Console.WriteLine("User {0} Call", this.GetType().Name);
    }
    public override void Text()
    {
        Console.WriteLine("User {0} Call", this.GetType().Name);
    }
}

荣耀类:

public class Honor : AbstractPhone
{
    public override void Call()
    {
        Console.WriteLine("User {0} Call", this.GetType().Name);
    }

    public override void Text()
    {
        Console.WriteLine("User {0} Call", this.GetType().Name);
    }
}

小米类:

public class Mi : AbstractPhone
{
    public override void Call()
    {
        Console.WriteLine("User {0} Call", this.GetType().Name);
    }
    public override void Text()
    {
        Console.WriteLine("User {0} Text", this.GetType().Name);
    }
}

努比亚类:

public class Lumia : AbstractPhone
{
    public override void Call()
    {
        Console.WriteLine("User {0} Call", this.GetType().Name);
    }
    public override void Text()
    {
        Console.WriteLine("User {0} Text", this.GetType().Name);
    }
}

学生类:(学生使用手机)

public class Student
{
        public int Id { get; set; }
        public string Name { get; set; }

        public void PlayiPhone(iPhone phone)
        {
            Console.WriteLine("这里是{0}", this.Name);
            phone.Call();
            phone.Text();
        }

        public void PlayLumia(Lumia phone)
        {
            Console.WriteLine("这里是{0}", this.Name);
            phone.Call();
            phone.Text();
        }

        public void PlayMi(Mi phone)
        {
            Console.WriteLine("这里是{0}", this.Name);
            phone.Call();
            phone.Text();
        }

        public void PlayHonor(Honor phone)
        {
            Console.WriteLine("这里是{0}", this.Name);
            phone.Call();
            phone.Text();
        }
}

调用学生玩手机:

public static void Show()
{
            Console.WriteLine("**************DIPShow***************");
            Student student = new Student()
            {
                Id = 191,
                Name = "小明"
            };
            {
                iPhone phone = new iPhone();
                student.PlayiPhone(phone);
            }
            {
                Lumia phone = new Lumia();
                student.PlayLumia(phone);
            }
            {
                Mi phone = new Mi();
                student.PlayMi(phone);
            }
            {
                Honor phone = new Honor();
                student.PlayHonor(phone);
            }
}

这里,“学生玩手机”模块,依赖了iphone,Lumia,Mi,Honor这些类,如果后面又出现了新手机品牌,比如oppo,vivo,那么又要回来修改student方法,添加PlayOppo,PlayVivo方法,这样这个学生就笨了,本来就打电话和发短信两个很简单的功能,现在出了一个新手机品牌,这个学生又要去学习一次。

所以,不能依赖于某个下层模块(具体手机品牌),修改:

学生类:

public class Student
{
        public int Id { get; set; }
        public string Name { get; set; }
        
        //不具体依赖具体类,而依赖于抽象
        public void Play(AbstractPhone phone)
        {
            Console.WriteLine("这里是{0}", this.Name);
            phone.Call();
            phone.Text();
        }
}

学生玩手机的类:

public static void Show()
{
            Console.WriteLine("**************DIPShow***************");
            Student student = new Student()
            {
                Id = 191,
                Name = "小明"
            };
            {
                AbstractPhone phone = new iPhone();
                phone.Call();
                student.Play(phone);
            }
            {
                AbstractPhone phone = new iPhone();
                student.Play(phone);
            }
            {
                AbstractPhone phone = new Lumia();
                //student.PlayLumia(phone);
                student.Play(phone);
            }
            {
                AbstractPhone phone = new Honor();
                //student.PlayHonor(phone);
                student.Play(phone);
            }

}

这样,就依赖到了抽象,

AbstractPhone phone = new Lumia();
student.Play(phone);

这两句,就达到了以前student.PlayLumia(phone)方法的效果,如果要达到以前student.PlayHonor(phone)的效果,只需要把Lumia换成Honor:

AbstractPhone phone = new Honor();
student.Play(phone);

所以,依赖导致就是面向抽象编程,层与层之间尽量写成抽象而不是细节。面向抽象编程的好处是更加灵活,更具有扩展性,因为面向的是抽象,可以传递不同的子类。

了解到依赖倒置的好处了,那么在工作当中如何用到它的好处呢?

1)低层模块都要有抽象,而不仅仅是细节(我们在分层结构中,DAL层,一定有个IDAL层,BLL层,一定由各IBLL层。)

2)变量的声明,尽量是面向抽象的,或者是基类。

比如:

AbstractPhone phone = new Honor();
student.Play(phone);

phone这个变量的声明称了抽象类,如果声明成 Honor phone = new Honor(); 左边固定是Honor类型了,右边就必须是new Honor,也就没办法扩展了,如果左边声明成抽象类或者基类,那么右边还有扩展空间。

5.接口隔离原则

是用接口还是抽象类?优先用接口,除非由代码需要继承,否则都可以用接口。

接口隔离原则:客户端不应该依赖它不需要的接口,一个类对另一个类的依赖应该建立在最小的接口上。

还举手机的例子:

我们定义一个IExtent的接口,里面有手机的常用功能,游戏、地图、上网......

public interface IExtend
{
        void Photo();
        void Online();
        void Game();
        void Record();
        void Movie();
        void Map();
        void Pay();
        void Wechat();
}

那么,荣耀手机,小米手机,苹果手机,都有上述这些功能,都可以实现这个接口,但努比亚手机不行,因为它的操作系统是微软的window mobile的,不支持微信,所以在具体实现中没办法实现微信接口,如果是这样的话,要分拆成两个接口:

public interface IExtend
{
        void Photo();
        void Online();
        void Game();
        void Record();
        void Movie();
        void Map();
        void Pay();
}
public interface IExtendWechat
{
    void Wechat();
}

荣耀手机,小米手机,苹果手机,同时继承IExtent和IExtentWechat接口,努比亚手机只继承IExtent接口。

划分接口的原则:首先不能大而全,违背了原则,但是也别太碎,要按照相似功能来划分,比如聊天接口可以包括微信和QQ,拍照可以和录音一起,因为拍照也能拍视频,等等。

6.开闭原则

开闭原则的意思是,对扩展开发,对修改封闭,只要增加类就能完成功能扩展,而不用修改原来的代码,这是一个理想化的奋斗目标,

总结:把共同点抽象出来做成接口,具体类实现它,要添加新功能,就新建类来扩展,而不是修改原来的代码。

程序设计从劣到优的做法:修改方法--->增加方法--->增加class--->增加DLL--->修改配置文件。

1)修改方法(比如第一个例子,牛呼吸空气,如果新增了鱼,鱼要呼吸水,那么就要修改呼吸这个方法)

2)增加方法(起码以前的那个呼吸空气的方法不受影响,只增加了呼吸水的方法)

但是修改方法和增加方法,都在修改类。

3)增加class(比如以前只有IPhone,后面可以增加Mi类,Honor类)

4)增加DLL(都不用重新编译生成、发布新的DLL文件,只需要增加DLL文件)

5)修改配置文件

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值