模式设计的原则

时间:2022-10-07 02:25:16

模式设计的原则

我们生活在一个充满规则的世界里,在复杂多变的外表下,万事万物都被永恒的真理支配,并有规律的运行着。模式也是一样,不论那种模式,其背后都潜藏着一些“永恒的真理”,这个真理就是设计原则。

记得一次参加微软的架构师培训,期间讲到设计模式,有人问了老师一个问题:“什么东西比设计模式更重要?”老师是一位有多年丰富实践经验的开发者,他毫不犹豫地回答到:“比模式更重要的是原则”。

单一职责原则(SRP)

“就一个类而言,应该仅有一个引起它变化的原因。”也就是说,不要把变化原因各不相同的职责放在一起,因为不同的变化会影响到不相干的职责。再通俗一点地说就是,不该你管的事情你不要管,管好自己的事情就可以了,多管闲事害了自己也害了别人。

例如:参考图1中的设计,图形计算程序只使用了正方形的Area()方法,永远不会使用Draw()方法,而它却跟Draw方法关联了起来。这违反了单一原则,如果未来因为图形绘制程序导致Draw()方法产生了变化,那么就会影响到本来毫不关系的图形计算程序。

那么我们该怎么做呢?如图2,将不同的职责分配给不同的类,使单个类的职责尽量单一,就隔离了变化,使他们互不影响。

开放―封闭原则(OCP)

“软件实体(类、模块、函数等)应该是可以扩展的,但是不可修改。”这个“不修改”的意思是“你可以随便增加新的类,但是不要修改原来的类”。从这个角度去理解就好多了,其实这里还是一个隔离变化的问题。

例如:如图3,有一个客户端程序通过数据访问接口操作数据,对于这套系统来说,一开始计划使用的是SQL Server或Oracle数据库,但是后来考虑到成本,改用免费的MySQL,那么对于客户端程序来说,后来数据的扩展对它没有任何影响。

图1 相互影响的设计

图2 修改后相互独立的设计

图3 通过接口实现OCP设计原则

图4 依赖于细节的开关设计

图5 修改后的通用的开关设计

图6 相互影响的操作

图7 通过接口使三个操作相互独立

依赖倒置原则(DIP)

“抽象不应该依赖于细节。细节应该依赖于抽象。”关于这个原则,还有种说法是,“高层不应该依赖于底层,两者都应该依赖于抽象。”其实怎么说都是对的,关键就是要理解一点,只有抽象的东西才是最稳定的,也就是说,我们依赖的是它的稳定。

例如:参考图4的设计,一个开关跟灯直接连接在一起了,也就是说开关依赖于灯的打开和关闭方法,那么如果我想用这个开关也可以打开其他东西呢,比如电视、音响。显然这个设计是无法满足这个要求了,因为我们依赖了细节而不是抽象,这个开关已经等价于“灯的开关”,而无法操作其他的设备。

那么我们该如何来设计一个通用的开关呢?参考图5的设计,现在我们不仅可以打开灯,还可以打开电视和音响,甚至未来实现了“开关接口”的任何东西。

接口隔离原则(ISP)

“不应该强迫客户依赖于它们不用的方法。接口属于客户,不属于它所在的类层次结构。”这个说得很明白了,就是不要强迫客户使用它们不用的方法。如果强迫用户使用它们不使用的方法,那么在这些不使用的方法发生改变时,客户往往不知如何应对。

例如:参考图6的设计,在这个设计里,取款、存款、转帐都使用一个通用界面接口,也就是说,每一个类都被强迫依赖了另两个类的接口方法,那么每个类有可能因为另外两个类的方法(跟自己无关)而被影响。拿取款来说,它根本不关心“存款操作”和“转帐操作”,可是它却要受到这两个方法的变化的影响。

那么我们该如何解决这个问题呢?参考图7的设计,为每个类都单独设计专门的操作接口,使得它们只依赖于它们关系的方法,这样就不会互相影响。

替换原则(LSP)

“子类型必须能够替换掉它们的基类型。”对于这个原则,通俗一些的理解就是,父类的方法都要在子类中实现或者重写,否则就不能算作继承。如果违反了LSP原则,常会导致在运行时的类型判断违反OCP原则。

例如:函数A的参数是基类型,调用时传递的对象是子类型,正常情况下,增加子类型都不会影响到函数A的。如果违反了LSP,则函数A必须小心的判断传进来的具体类型,否则就会出错,这就已经违反了OCP原则。

上一篇:“墙”内更安全 下一篇:不要麻烦 只要便利