赞
踩
面向对象设计的原则是面向对象思想的提炼,它比面向对象思想的核心要素更具可操作性,但与设计模式相比,却又更加的抽象,是设计精神要义的抽象概括。形象地将,面向对象思想像法理的精神,设计原则则相对于基本宪法,而设计模式就好比各式各样的具体法律条文了。
面向对象设计原则有 6 个:开放封闭原则,单一职责原则,依赖倒置原则,Liskov 替换原则,迪米特法则和接口隔离原则或合成/聚合复用原则(不同资料略有不同,这里对7个都做了整理)。
There should never be more than one reason for a class to change. 什么意思呢?
所谓单一职责原则就是一个类只负责一个职责,只有一个引起变化的原因。
如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化会削弱或抑制这个类完成其他职责的能力,这个耦合会导致脆弱的设计。
软件设计真正要做的许多内容,就是发现职责并把这些职责相互分离;如果能够想到多于一个动机去改变一个类,那么这个类就具有多于一个职责,就应该考虑类的分离。
以调制解调器为例如下图:
这样以来,无论单独修改连接部分还是单独修改数据传送部分,都彼此互不影响。
总结单一职责优点:
降低类的复杂性,
提高可维护性
提高可读性。
降低需求变化带来的风险。需求变化是不可避免的,如果单一职责做的好,一个接口修改只对相应的实现类有影响,对其它的接口无影响,这对系统的扩展性和维护性都有很大的 帮助。
枪的主要职责是射击,如何射击在各个具体的子类中定义。注意在类中调用其他类时务 必调用父类或接口,如果不能掉话父类或接口,说明类的射击已经违反了 LSP 原则。
如果我们有一个玩具手 枪,该如何定义呢?我们先在类图 2-1 上增加一个类 ToyGun, 然后继承于 AbstractGun 类,修改后的类图如下:
玩具枪是不能用来射击的,杀不死人的,这个不应该写 shoot 方法,在这种情况下业 务的调用类就会出现问题。为了解决这个问题,ToyGun 可以脱离继承,建立一个独立的父 类,为了做到代码可以服用,可以与 AbstractGun 建立关联委托关系,如下图:
因此,如果子类不能完整地实现父类的方法,那么建议断开父子继承关系,采用依赖,聚合, 组合等关系代替继承。
子类可以有自己的属性或方法。
覆盖或实现父类的方法时输入的参数可以放大。
覆盖或实现父类的方法时输出结果可以被缩小。这是什么意思呢,父类的方法返回值是一个类型 T,子类相同的方法(覆写)的返回值为类型 S,那么根据里氏替换原则就要求 S 必须小于 等于 T,也就是说要么 S 和 T 是同一个类型,要么 S 是 T 的子类型。
采用里氏替换原则的目的就是增加程序的健壮性,需求变更时也可以保持良好的兼容性和稳 定性,即使增加子类,原有的子类可以继续运行。在实际项目中,每个子类对应不同的业务含义, 使用父类作为参数,传递不同的子类完成不同业务逻辑。
- public class HondaCar {
- public void Run() {
- Console.WriteLine("本田车启动了!");
- }
-
- public void Turn() {
- Console.WriteLine("本田车拐弯了!");
- }
-
- public void Stop() {
- Console.WriteLine("本田车停止了!");
- }
- }
-
- public class FordCar {
- public void Run() {
- Console.WriteLine("福特车启动了!");
- }
-
- public void Turn() {
- Console.WriteLine("福特车拐弯了!");
- }
-
- public void Stop() {
- Console.WriteLine("福特车停止了!");
- }
- }
-
- public class AutoSystem {
- public enum CarType {
- Ford, Fonda
- }
-
- private HondaCar hondcar = new HondaCar();
- private FordCar fordcar = new FordCar();
- private CarType type;
-
- public AutoSystem(CarType carType) {
- this.type = carType;
- }
-
- public void RunCar() {
- if (this.type == CarType.Fonda) {
- hondcar.Run();
- } else if (this.type == CarType.Ford) {
- fordcar.Run();
- }
- }
-
- public void StopCar() {
- if (this.type == CarType.Fonda) {
- hondcar.Stop();
- } else if (this.type == CarType.Ford) {
- fordcar.Stop();
- }
- }
-
- public void TurnCar() {
- if (this.type == CarType.Fonda) {
- hondcar.Turn();
- } else if (this.type == CarType.Ford) {
- fordcar.Turn();
- }
- }
- }
显然这个实现代码也可满足现在的需求。- public class AutoSystem {
- public enum CarType {
- Ford, Fonda, Jeep
- }
-
- private HondaCar hondcar = new HondaCar();
- private FordCar fordcar = new FordCar();
- private Jeep jeep = new Jeep();
- private CarType type;
-
- public AutoSystem(CarType carType) {
- this.type = carType;
- }
-
- public void RunCar() {
- if (this.type == CarType.Fonda) {
- hondcar.Run();
- } else if (this.type == CarType.Ford) {
- fordcar.Run();
- } else if (this.type == CarType.Jeep) {
- jeep.Run();
- }
- }
-
- public void StopCar() {
- if (this.type == CarType.Fonda) {
- hondcar.Stop();
- } else if (this.type == CarType.Ford) {
- fordcar.Stop();
- } else if (this.type == CarType.Jeep) {
- jeep.Stop();
- }
- }
-
- public void TurnCar() {
- if (this.type == CarType.Fonda) {
- hondcar.Turn();
- } else if (this.type == CarType.Ford) {
- fordcar.Turn();
- } else if (this.type == CarType.Jeep) {
- jeep.Turn();
- }
- }
- }
通过代码分析得知,上述代码也确实满足了需求,但是软件是不断变化的,软件的需求也是 变化的,如果将来业务又扩大了,该自动驾驶系统还有能实现通用、三菱、大众汽车,这 样我们不得不又要修改AutoSystem类了。这样会导致系统越来越臃肿,越来越大, 而且依赖越来越多低层模块,只有低层模块变动,AutoSystem类就不得不跟着变 动,导致系统设计变得非常脆弱和僵硬。- public interface ICar {
- void Run();
- void Stop();
- void Turn();
- }
- public class HondaCar:ICar {
- public void Run() {
- Console.WriteLine("本田车启动了!");
- }
- public void Turn() {
- Console.WriteLine("本田车拐弯了!");
- }
- public void Stop() {
- Console.WriteLine("本田车停止了!");
- }
- }
- public class FordCar :ICar {
- public void Run() {
- Console.WriteLine("福特车启动了!");
- }
- public void Turn() {
- Console.WriteLine("福特车拐弯了!");
- }
- public void Stop() {
- Console.WriteLine("福特车停止了!");
- }
- }
- public class Jeep:ICar {
- public void Run() {
- Console.WriteLine("福特车启动了!");
- }
- public void Turn() {
- Console.WriteLine("福特车拐弯了!");
- }
- public void Stop() {
- Console.WriteLine("福特车停止了!");
- }
- }
- public class AutoSystem {
- private ICar car;
- public AutoSystem(ICar car) {
- this.car = car;
- }
- public void RunCar() {
- this.car.Run();
- }
- public void StopCar() {
- this.car.Stop();
- }
- public void TurnCar() {
- this.car.Turn();
- }
- }
现在Autosystem系统依赖于ICar这个抽象,而与具体的实现细节HondaCar:和FordCar无关,所以实现细节的变化不会影响AutoSystem.对于实现细节只要实现ICar即可。 即实现细节依赖于ICar抽象。- public class Teacher {
- public void commond(GroupLeader groupLeader) {
- List<Girl> listGirls = new ArrayList<Girl>();
- for (int i = 0; i < 20; i++) {
- listGirls.add(new Girl());
- }
- groupLeader.countGirls(listGirls);
- }
- }
- public class Teacher {
- public void commond(GroupLeader groupLeader) {
- groupLeader.countGirls();
- }
- }
- public class GroupLeader {
- private List<Girl> listGirls;
-
- public GroupLeader(List<Girl> _listGirls) {
- this.listGirls = _listGirls;
- }
-
- public void countGirls() {
- System.out.println("女生数量是:" + listGirls.size());
- }
- }
- import com.sun.security.ntlm.Client;
-
- public class BankProcess {
- // 存款
- public void Deposite() {
- }
-
- // 取款
- public void Withdraw() {
- }
-
- // 转账
- public void Transfer() {
- }
- }
-
- public class BankStaff {
- private BankProcess bankpro = new BankProcess();
-
- public void BankHandle(Client client) {
- switch (client.Type) {
- // 存款
- case "deposite":
- bankpro.Deposite();
- break;
- // 取款
- case "withdraw":
- bankpro.Withdraw();
- break;
- // 转账
- case "transfer":
- bankpro.Transfer();
- break;
- }
-
- }
- }
这种设计显然是存在问题的,目前设计中就只有存款,取款和转账三个功能,将来如果 业务增加了,比如增加申购基金功能,理财功能等,就必须要修改 BankProcess 业务类。我 们分析上述设计就不能发现把不能业务封装在一个类里面,违反单一职责原则,而有新的需 求发生,必须修改现有代码则违反了开放封闭原则。- public interface IBankProcess {
- void Process();
- }
- public class DepositProcess : IBankProcess {
- public void Process() {
- //办理存款业务
- Console.WriteLine("Process Deposit");
- }
- }
- public class WithDrawProcess : IBankProcess{
- public void Process() {
- //办理取款业务
- Console.WriteLine("Process WithDraw");
- }
- }
- public class TransferProcess : IBankProcess {
- public void Process() {
- //办理转账业务
- Console.WriteLine("Process Transfer");
- }
- }
- public class BankStaff {
- private IBankProcess bankpro = null;
- public void BankHandle(Client client) {
- switch (client.Type) {
- //存款
- case "Deposit":
- bankpro = new DepositUser(); break;
- //转账
- case "Transfer":
- bankpro = new TransferUser(); break;
- //取款
- case "WithDraw":
- bankpro = new WithDrawUser(); break;
- }
- bankpro.Process(); }
- }
- }
这样当业务变更时,只需要修改对应的业务实现类就可以,其他不相干的业务就不必修 改。当业务增加,只需要增加业务的实现就可以了。
- public interface IEnumerable{
- IEnumerator GetEnumerator();
- }
- public interface ICollection : IEnumerable{
- void CopyTo(Array array, int index);
- // 其余成员略
- }
- public interface IList : ICollection, IEnumerable{
- int Add(object value);
- void Clear();
- bool Contains(object value);
- int IndexOf(object value);
- void Insert(int index, object value);
- void Remove(object value);
- void RemoveAt(int index);
- // 其余成员略
- }
如果不采用这样的接口继承方式,而是定义一个总的接口包含上述成员,就无法实现 IEnumerable 接口、 ICollection 接口与 IList 接口成员之间的隔离。假如这个总接口名为 IGeneralList,它抹平了 IEnumerable 接口、ICollection 接口与 IList 接口之间的差别,包含了它们的所有方法。现在,如果我们需要定义一 个 Hashtable 类。根据数据结构的特性,它将无法实现 IGeneralList 接口。因为 Hashtable 包含的 Add() 方法,需要提供键与值,而之前针对 ArrayList 的 Add()方法,则只需要值即可。这意味着两者的接口存 在差异。我们需要专门为 Hashtable 定义一个接口,例如 IDictionary,但它却与 IGeneralList 接口不存 在任何关系。正是因为一个总接口的引入,使得我们在可枚举与集合层面上丢失了共同的抽象意义。虽然 Hashtable 与 ArrayList 都是可枚举的,也都具备集合特征,它们却不可互换。Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。