首先放出早先写的面向对象七大原则,以前不了解的同学建议先大概看一遍~
有说的不正确或者不准确的地方欢迎留言指正
有什么有趣的写作技巧或者想法欢迎大家给我留言,大家的帮助是我写下去最有效的动力
下面笔者跟大家聊一聊每一条原则到底是个什么东东
单一职责原则(Single Responsibility Principle)
见名知意,这个条职责的潜台词的就是,专注做一个事、专注做一个事、专注做一个事,重要的事说三遍。别的事情我都不管,我只负责我的事情,专注干好自己的东西。
那单一职责用在程序中有什么好处呢?
- 提高可读性,想想10个功能放在一个类里面写方便阅读还是分10个类方便阅读?
- 出现BUG时可以缩小出错的范围,更好的定位,哪个功能出错找哪个功能,早日和BUG说ByeBye~
- 修改逻辑时避免造成其他功能模块错误,例如折扣数值更改0.8,你不小心把极品装备爆率改成0.8,那你就等死吧
里氏替换原则(Liskov Substitution Principle)
通俗的讲就是老子不在可以用儿子顶替,具体这条原则怎么个好法请看下面的示例
基础代码
public abstract class Phone
{
public abstract void Call();
}
interface Android{ }
interface IOS{ }
public class OnePlus : Phone, Android
{
public override void Call()
{
Debug.Log($"{nameof(OnePlus)}进行通话。。。。。");
}
}
public class Pixel : Phone, Android
{
public override void Call()
{
Debug.Log($"{nameof(Pixel)}进行通话。。。。。");
}
}
public class XiaoMi : Phone, Android
{
public override void Call()
{
Debug.Log($"{nameof(XiaoMi)}进行通话。。。。。");
}
}
public class Apple : Phone, IOS
{
public override void Call()
{
Debug.Log($"{nameof(Apple)}进行通话。。。。。");
}
}
不使用里氏替换,调用call函数需要为每个类型的手机写一个函数
public void WantToCall_0(OnePlus phone)
{
phone.Call();
}
public void WantToCall_1(Pixel phone)
{
phone.Call();
}
public void WantToCall_2(XiaoMi phone)
{
phone.Call();
}
public void WantToCall_3(Apple phone)
{
phone.Call();
}
使用里氏替换,只需要一个函数全部搞定,在后面的【桥接模式】会广泛用到的
public void WantToCall_4(Phone phone)
{
phone.Call();
}
依赖倒置原则(Dependence Inversion Principle)
定义:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。 依赖倒置原则的核心思想是面向接口编程。
下面我们先解释接个名词,什么是高层模块,什么是低层模块?什么是细节,什么是抽象?
在项目中我们经常会有一些数学函数库,或者工具类(Log日志工具),这些封装好的工具会被我们业务逻辑模块经常调用,那么这些工具函数库就是低层模块,业务逻辑模块就是高层模块。
细节的意思就是具体的实现,例如上面里氏替换中打电话的例子,在不使用里氏替换的时候需要定义4种打电话函数,这就是依赖细节,其中的细节就是“OnePlus ”、“Pixel ”、“XiaoMi”、“Apple ”,反之抽象就是Phone
下面笔者就举一个例子来说明,示例是这样的,华硕和微型都可使用不同型号的显卡,反之不同型号的显卡也可以使用在不同品牌的主板上。利用依赖倒置原则,这种规则的实现变得很简单也很灵活~
基础代码
//显卡
public interface IGraphicsCard
{
void BeginWork(IMainboard mainboard);
}
//主板
public interface IMainboard
{
void GetElectricity();
void DrawPicture(IGraphicsCard graphicsCard);
}
public class NVIDIA_2018Ti : IGraphicsCard
{
public NVIDIA_2018Ti(IMainboard mainboard)
{
BeginWork(mainboard);
}
public void BeginWork(IMainboard mainboard)
{
mainboard.GetElectricity();
Debug.Log($"NVIDIA_2018Ti获取{mainboard.GetType()}电量后开始工作");
}
}
public class NVIDIA_2018 : IGraphicsCard
{
public NVIDIA_2018(IMainboard mainboard)
{
BeginWork(mainboard);
}
public void BeginWork(IMainboard mainboard)
{
mainboard.GetElectricity();
Debug.Log($"NVIDIA_2018获取{mainboard.GetType()}电量后开始工作");
}
}
//华硕主板
public class Asus : IMainboard
{
public void DrawPicture(IGraphicsCard graphicsCard) { }
public void GetElectricity() { }
}
//微型主板
public class MSI : IMainboard
{
public void DrawPicture(IGraphicsCard graphicsCard) { }
public void GetElectricity() { }
}
实现代码,这种2*2种模式的实现非常简单~
public void DrawPicture()
{
IMainboard aSus = new Asus();
aSus.DrawPicture(new NVIDIA_2018Ti(aSus));
aSus.DrawPicture(new NVIDIA_2018(aSus));
IMainboard mSI = new MSI();
mSI.DrawPicture(new NVIDIA_2018Ti(mSI));
mSI.DrawPicture(new NVIDIA_2018(mSI));
}
接口隔离原则(Interface Segregation Principle)
通俗的讲就是定义的接口尽量按照功能细分,比如打电话功能一个接口,上网一个接口,发短信一个接口,分的够细不仅职能明确,也不会因为使用某种职能必须实现一些没必要的功能。总之通过分散定义多个接口,可以预防外来变更的扩散,提高系统的灵活性和可维护性。
我们可以先参考下微软他们的做法,都是按职能划分,而且划分的很细
迪米特法则(Law Of Demeter)也叫【最少知识原则】
他想表达的意思是能用 private、protected的就不要用public,不要过多的暴露自己的内容,而且对应类与类之间的关系,尽量越少越好。后面讲到的【门面模式】和【中介者模式】想表达的也是这个意思。
迪米特法则其根本思想,是强调了类之间的松耦合。类之间的耦合越弱,一个处于弱耦合的类被修改,不会对有关系的类造成波及。
在下面的示例中说明的是GameObject扩展Log方法都写在了Object上面,这种情况就违背了迪米特法则。因为其他的类不需要这个Log扩展,这也就破坏了原来的结构,侵入性太强了。如果所有的扩展都是以Object为基准,那么调用函数的时候就会出现数不过来的函数下拉条目~~~~
public static class Exted
{
public static void CustomerLog_Obj0(this GameObject Obj) { }
public static void CustomerLog_Obj1(this object Obj) { }
public static void CustomerLog_Obj2(this object Obj) { }
public static void CustomerLog_Obj3(this object Obj) { }
public static void CustomerLog_Obj4(this object Obj) { }
}
开闭原则(Open Close Principle)
开放封闭原则主要体现在对扩展开放、对修改封闭,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况.封装变化,是实现开放封闭原则的重要手段,对于经常发生变化的状态,一般将其封装为一个抽象,拒绝滥用抽象,只将经常变化的部分进行抽象。
通俗的讲在功能变动的时候,尽量以增量补丁的形式更改,也就是原来的东西尽量原封不动,继承原来的东西在添加。比如原来的东西作为父类,新的更改在子类里面实现。
组合/聚合复用原则(Composite/Aggregate Reuse Principle CARP)
引用老版本的话~
因为:其实整个设计模式就是在讲如何类与类之间的组合/聚合。在一个新的对象里面通过关联关系(包括组合关系和聚合关系)使用一些已有的对象,使之成为新对象的一部分,新对象通过委派调用已有对象的方法达到复用其已有功能的目的。也就是,要尽量使用类的合成复用,尽量不要使用继承。
如果为了复用,便使用继承的方式将两个不相干的类联系在一起,违反里氏代换原则,哪是生搬硬套,忽略了继承了缺点。继承复用破坏数据封装性,将基类的实现细节全部暴露给了派生类,基类的内部细节常常对派生类是透明的,白箱复用;虽然简单,但不安全,不能在程序的运行过程中随便改变;基类的实现发生了改变,派生类的实现也不得不改变;从基类继承而来的派生类是静态的,不可能在运行时间内发生改变,因此没有足够的灵活性。
所以:组合/聚合复用原则可以使系统更加灵活,类与类之间的耦合度降低,一个类的变化对其他类造成的影响相对较少,因此一般首选使用组合/聚合来实现复用;其次才考虑继承,在使用继承时,需要严格遵循里氏代换原则,有效使用继承会有助于对问题的理解,降低复杂度,而滥用继承反而会增加系统构建和维护的难度以及系统的复杂度,因此需要慎重使用继承复用。