第二讲:面向对象原则
学习oop就是学习一系列的原则,技巧,经验,模式,让程序高可用,高可维护,高性能
第一: 单一职责SRP,一个对象完成的任务是明确的,不能是万能类
第二: 开闭原则OCP,开放扩展,关闭修改(项目的需求变化,不能影响程序的修改)
第三: 替换原则LSP, 父类可以被子类替换,是我们实现多态的基础
第四: 迪米特原则 LOD,最小知道原则,用于对象解耦
第五: 接口隔离原则ISP,尽量是小接口,不要涉及万能接口
第六: 依赖倒置DIP, 高层模块不依赖底层模块, 都依赖抽象
一: 单一职责
概念: 一个对象完成的东西是明确的,一个类值负责一个功能,
好处: 个各类各负其责,一个类的修改,不会影响整个程序,(但是要是修过错了那整个项目都会有问题,)
对象: 小到类(普通类,接口),大到模块
二: 替换原则LSP
概念: 子类必须可以替换父类类型,反过来不行
例子: 人,可以被男人,或者女人替换, (父类 变量=子类对象)
好处: 子类可以替换父类,可以保护父类,并且子类可以扩展新的功能
namespace LspKoiv.LSP
{
/// <summary>
/// 父类
/// </summary>
public abstract class Lspzzl
{
public string Brand { get; set; }
public int Price { get; set; }
/// <summary>
/// 抽象,子类必须重写
/// </summary>
public abstract void Print();
public void Copy()
{
Console.WriteLine("我是父类方法Copy");
}
/// <summary>
/// 虚方法,子类可以重写,也可以不重写
/// </summary>
public virtual void Scan()
{
Console.WriteLine("我是父类虚方法Scan");
}
}
}
namespace LspKoiv.LSP
{
/// <summary>
/// 子类
/// </summary>
public class HpLsp: Lspzzl
{
public override void Print()
{
Console.WriteLine("HpLsp:重写了父类的抽象方法Print");
}
public override void Scan()
{
base.Scan();
Console.WriteLine("HpLsp:重写了父类的虚方法Scan,当然可以不重写");
}
public new void Copy()
{
int i = 9;
Console.WriteLine("HpLsp:覆盖了父类的Copy方法"+i);
}
//可以扩展子类功能
}
}
namespace LspKoiv.LSP
{
/// <summary>
/// 子类
/// </summary>
public class JnLsp:Lspzzl
{
public override void Print()
{
Console.WriteLine("JnLsp:只重写了父类的Print");
}
}
}
namespace LspKoiv
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("面向对象替换原则-----------------------------------子类替换父类");
Lspzzl myLsp = new HpLsp();
//
myLsp.Copy(); //调用的事父类的方法 (覆盖的方法),继承用父类,重写用子类
myLsp.Print(); //调用的子类方法 (重写的抽象方法)
myLsp.Scan(); //调用的子类方法,也可以在子类调用父类 (重写的抽象方法)
Console.WriteLine("面向对象替换原则-----------------------------------子类替换父类(覆盖的方法调用和对象声明的空间有关)");
HpLsp myLsp2 = new HpLsp();
myLsp2.Copy(); //调用的是,子类的Copy,因为这个对象是子类
Console.WriteLine("利用反射查看------------------------------------");
typeof(Lspzzl).GetMethod("Copy").Invoke(myLsp, null);
typeof(HpLsp).GetMethod("Copy").Invoke(myLsp, null);
//结论:1:父类的普通方法调用,如果被子类覆盖时,方法调用,取决于反射的类型是父类还是子类(就是对象声明的空间)
// 2:父类的抽象方法或虚方法被重新,就直接调用子类方法
//new一般应用在,控件二次开发,
//替换原则是实现开闭原则的重要方法
Console.Read();
}
}
}
三:迪米特原则和开闭原则
迪米特原则:最小化原则,当两个类直接直接通信的时候,会导致两个类高度依赖(高耦合),我们应该尽量避免两个类直接接触(低耦合),可以使用第三者做转发(高内聚低耦合)
如我们买东西时候的 用户-->客服-->售后(或销售), 迪米特原则就是实现类的解耦
体现: 封装实现,尽量降低成员权限,而不是public,一个类依赖另一个类越少越好
应用: (工厂设计模式,中介者模式,工厂模式,外观模式)
开闭原则: 开发扩展,封闭修改
namespace LODKoiv
{
/// <summary>
/// 父接口
/// </summary>
public interface IPrint
{
void Print();
void Copy();
}
}
namespace LODKoiv
{
public class HpPrint : IPrint
{
public void Copy()
{
Console.WriteLine("HpPrint在复印");
}
public void Print()
{
Console.WriteLine("HpPrint在打印");
}
}
}
namespace LODKoiv
{
public class JnPrint:IPrint
{
public void Copy()
{
Console.WriteLine("JnPrint在复印");
}
public void Print()
{
Console.WriteLine("JnPrint在打印");
}
}
}
namespace LODKoiv
{
/// <summary>
/// 工厂类
/// </summary>
public class Sim
{
/// <summary>
/// 这个是配置的,我们这里写死
/// </summary>
private static string printType = "JnPrint";
/// <summary>
/// 创建
/// </summary>
/// <returns></returns>
public static IPrint CreatePring()
{
return (IPrint)Assembly.Load("LODKoiv").CreateInstance("LODKoiv." + printType);
}
}
}
namespace LODKoiv
{
class Program
{
static void Main(string[] args)
{
//迪米特原则,和开放封闭原则
// IPrint myprint = new HpPrint();
IPrint myprint = Sim.CreatePring(); //这个方法返回什么对象我们不关心,(简单的工厂模式)
myprint.Copy();
Console.Read();
}
}
}
class A{ A1公共方法(){} B1公共方法(){}}
class C:A
{
D1(){}
}
class D:A
{
F1(){}
}
以上写法主要作用是:代码复用,减少冗余!
A a1=new C();
A a2=new D();
以上用法:可以?可以!但是,好吗?有必要吗?不好!根本没有必要!实际应用没有价值! (a1,a2调用不了子类的方法)
所有的类都继承自object类
object o=new C();
所以替换原则: 一般使用与有抽象方法的类,或方法
-----------------------------------------------------------------------------------------------------------------
学员问题:如果后期有新需求是不是还是得回到父类和接口区添加新的抽象方法?
首先:如果直接到父类或接口添加新的抽象方法或接口,那么违背开闭原则!
如果现实中真的有这种情况,那没啥好办法,你就得把所有的实现类或者子类都得修改!但是我们尽量避免!因为
如果一个项目经常遇到这种情况,说明你设计的不好!
另外的解决方案:使用扩展方法。还有,我们后面学习AOP面向切面编程,如果你在实现中,有些需求无法预料,
或者目前无法完全确定,这时候就应该考虑动态扩展。DI,Ioc容器。
假如:一个接口类有5个接口方法,然后有若干实现类,但是后续需要增加一个接口,但是这个接口并不是所有实现类
都需要的。这种情况绝对不能直接修改原有接口。因为你修改后,所有的实现类都要变化,引起“代码地震”。
解决方法:设计小接口,谁有需求谁使用!
PS:继承在实际开发中,尽量少用!用接口。如果我们侧重于代码的复用,则这部分用继承。如果侧重功能的扩展,则使用接口!
----------------------------------------------------------依赖倒置原则---------------------------------------------------------------------
依赖倒置: (DIP),高层模块不依赖底层模块,两个都依赖抽象(接口/抽象类),抽象不应该依赖细节(具体实现), 但是细节依赖抽象
例如: 电脑主板,各种模块,都是按照主板接口实现的,谁是抽象部分,:主板的各种插槽,内存条,硬盘
体现:面向抽象编程就是我们说的依赖倒置(DIP)
-------------------------------------------------------------接口隔离原则------------------------------------------------------------------------
接口隔离: 一个接口类的设计必须严格准守 "单一职责",不能设计万能接口, 但是也不能接口泛滥
Q:拆分多少合适,怎么拆分
namespace DIpkoiv
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("-----------------------面向过程变成(面向细节)----------------------------");
KDip1.Stend k = new KDip1.Stend();
k.XueOne(new KDip1.XuexiOne());
k.XueTwo(new KDip1.XuexiTwo());
k.XueSee(new KDip1.XuexiSee());
//面向细节变成,第一代码冗余多, 修改不方便,违反了开闭原则, 每次要是有一个新的学科,都要修改Stend,添加新的方法,每次修改都要吧流程做一遍
Console.WriteLine("-----------------------面向对象(依赖倒置)----------------------------");
KDip2.Stend s = new KDip2.Stend();
s.Xx(new KDip2.XuexiOne() { KTime = 30, XueXiName = ".Net高级" });
s.Xx(new KDip2.XuexiTwo() { KTime = 60, XueXiName = "Go人工智能" });
s.Xx(new KDip2.XuexiSee() { KTime = 50, XueXiName = "Python大数据" });
//依赖倒置, Stend类不依赖具体的XuexiOne等类,而是依赖的抽象类, 这样不管我们添加多少类,只要实现了抽象类就可以,Stend不需要修改(开放扩展,关闭修改)
Console.Read();
}
}
}
-----------------------面向过程变成(面向细节)----------------------------
namespace KDip1
{
/// <summary>
/// 考试类
/// </summary>
public class Stend
{
/// <summary>
/// 第一种学科考试
/// </summary>
/// <param name="o"></param>
public void XueOne(XuexiOne o)
{
o.XueXi();
o.TiJiao();
o.KaoShi();
}
/// <summary>
/// 第二种学科考试
/// </summary>
/// <param name="o"></param>
public void XueTwo(XuexiTwo o)
{
o.XueXi();
o.TiJiao();
o.KaoShi();
}
/// <summary>
/// 第三种学科考试
/// </summary>
/// <param name="o"></param>
public void XueSee(XuexiSee o)
{
o.XueXi();
o.TiJiao();
o.KaoShi();
}
}
}
namespace KDip1
{
public class XuexiOne
{
public string XueXiName =".Net高级";
public int KTime=30;
/// <summary>
/// 学习方法
/// </summary>
public void XueXi()
{
Console.WriteLine("我在学习" + XueXiName + "课时是:" + KTime);
}
/// <summary>
/// 提交作业
/// </summary>
public void TiJiao()
{
Console.WriteLine("提交作业" + XueXiName + "课时是:" + KTime);
}
/// <summary>
/// 考试
/// </summary>
public void KaoShi()
{
Console.WriteLine("开始考试" + XueXiName + "课时是:" + KTime);
}
}
}
namespace KDip1
{
public class XuexiTwo
{
public string XueXiName ="Go人工智能";
public int KTime=50;
/// <summary>
/// 学习方法
/// </summary>
public void XueXi()
{
Console.WriteLine("我在学习" + XueXiName + "课时是:" + KTime);
}
/// <summary>
/// 提交作业
/// </summary>
public void TiJiao()
{
Console.WriteLine("提交作业" + XueXiName + "课时是:" + KTime);
}
/// <summary>
/// 考试
/// </summary>
public void KaoShi()
{
Console.WriteLine("开始考试" + XueXiName + "课时是:" + KTime);
}
}
}
namespace KDip1
{
public class XuexiSee
{
public string XueXiName ="Python大数据";
public int KTime=46;
/// <summary>
/// 学习方法
/// </summary>
public void XueXi()
{
Console.WriteLine("我在学习" + XueXiName + "课时是:" + KTime);
}
/// <summary>
/// 提交作业
/// </summary>
public void TiJiao()
{
Console.WriteLine("提交作业" + XueXiName + "课时是:" + KTime);
}
/// <summary>
/// 考试
/// </summary>
public void KaoShi()
{
Console.WriteLine("开始考试" + XueXiName + "课时是:" + KTime);
}
}
}
-----------------------面向对象(依赖倒置)----------------------------
namespace KDip2
{
/// <summary>
/// 考试类
/// </summary>
public class Stend
{
/// <summary>
/// 依赖倒置
/// </summary>
/// <param name="x"></param>
public void Xx(XBase x)
{
x.XueXi();
x.TiJiao();
x.KaoShi();
}
}
}
namespace KDip2
{
public abstract class XBase
{
public string XueXiName {get;set;}
public int KTime {get;set;}
/// <summary>
/// 学习方法
/// </summary>
public abstract void XueXi();
/// <summary>
/// 提交作业
/// </summary>
public abstract void TiJiao();
/// <summary>
/// 考试
/// </summary>
public abstract void KaoShi();
}
}
namespace KDip2
{
public class XuexiOne:XBase
{
/// <summary>
/// 学习方法
/// </summary>
public override void XueXi()
{
Console.WriteLine("我在学习" + XueXiName + "课时是:" + KTime);
}
/// <summary>
/// 提交作业
/// </summary>
public override void TiJiao()
{
Console.WriteLine("提交作业" + XueXiName + "课时是:" + KTime);
}
/// <summary>
/// 考试
/// </summary>
public override void KaoShi()
{
Console.WriteLine("开始考试" + XueXiName + "课时是:" + KTime);
}
}
}
namespace KDip2
{
public class XuexiTwo:XBase
{
/// <summary>
/// 学习方法
/// </summary>
public override void XueXi()
{
Console.WriteLine("我在学习" + XueXiName + "课时是:" + KTime);
}
/// <summary>
/// 提交作业
/// </summary>
public override void TiJiao()
{
Console.WriteLine("提交作业" + XueXiName + "课时是:" + KTime);
}
/// <summary>
/// 考试
/// </summary>
public override void KaoShi()
{
Console.WriteLine("开始考试" + XueXiName + "课时是:" + KTime);
}
}
}
namespace KDip2
{
public class XuexiSee:XBase
{
/// <summary>
/// 学习方法
/// </summary>
public override void XueXi()
{
Console.WriteLine("我在学习" + XueXiName + "课时是:" + KTime);
}
/// <summary>
/// 提交作业
/// </summary>
public override void TiJiao()
{
Console.WriteLine("提交作业" + XueXiName + "课时是:" + KTime);
}
/// <summary>
/// 考试
/// </summary>
public override void KaoShi()
{
Console.WriteLine("开始考试" + XueXiName + "课时是:" + KTime);
}
}
}
----------------总结:-------------------------------------------------------------------------------------------------
1.单一职责(SRP):对象职责单一化
2.开闭原则:对象修改最小化
3.里氏替换原则:程序扩展具体方法
4.迪米特法则:实现高内聚低耦合
5.依赖倒置原则:“下面”依赖于“上面”。面向抽象编程的根本!
6.接口隔离原则:接口设计要适中。