面向对象设计原则

来源:互联网 发布:网络选修课 编辑:程序博客网 时间:2024/06/08 00:22

@Author:云都小生

这篇文章几乎都是理论,所以,做好接受的心理准备···


面向对象解决的问题



可扩展性、可维护性和可复用性,是面向对象要解决的其中几个核心问题。同时,面向对象设计还追求两个核心的点:高内聚、低耦合

面向对象设计的好处有很多,其中有一点体会:使用这种思想,能够更快的进行开发,更廉价的进行维护。

今天我们需要了解一些面向对象设计中的基本原则,这些基本原则背后的核心也是为了追求高内聚、低耦合


单一职责原则



我还记得我在刚开始学Java类与对象的时候,经常会很多方法写在一个类里面,导致一个类乱七八糟。

我写的里面提供了各种各样的方法,有一些方法还是没有关系的,这样会使得一个类的功能模糊不清。

后来累积了一段时间,我把实现相应功能的都放在同一个类里面,这下舒服多了。

但是,这却不是最优的做法。把实现同一个功能的许多方法(职责)放在同一个类里,有时候其中一个方法变了,其他方法也得跟着受影响。所以我们需要了解单一职责原则。

单一职责原则告诉我们:一个类不能太“累”,一个类只能负责一个功能领域中的相应职责。

例如有这样一个邮件管理系统类,这个类中原本提供了查询邮件、发送邮件、删除邮件的方法,原本是同一个功能领域(邮件)的不同方法,但是我们还是选择把它们分开成不同的类。

如果是在同一个类中,发送邮件的方法会调用到查询邮件的方法,那可能就会影响到查询方法的操作。所以我们希望把这些职责给分开,追求高内聚、低耦合的目标。

这个原则理解起来最简单,但是运用起来却是最难的,因为有些设计者经常会区分不了不同职责。你需要的是,不断的累积这方面的经验。


开闭原则



开闭原则(Open-Close)其实很简单,就是“对扩展开放,对修改关闭”。

对于一个软件来说,如果我们在扩展一些新东西的时候,需要修改到之前的代码,那就违背了这个原则。我们来看这样一个例子。

interface Test {    public void prinTest();}public class Test1 implements Test{    public void prinTest() {        System.out.println("测试1");    }}public class Test2 implements Test{    public void prinTest() {        System.out.println("测试2");    }}public class NewTest {    public static Test creater(String type)    {        if(type.equals("Test1"))        {            return new Test1();        }        else if(type.equals("Test2"))        {            return new Test2();        }        else        {            return null;        }    }}public class Client {    public static void main(String[] args)    {        Test t1 = NewTest.creater("Test1");        t1.prinTest();    }}

一旦我们想新增加Test2,就需要修改NewTest类的逻辑,这样就违背了开闭原则

那我们要怎么去设计呢?

interface Test {    public void prinTest();}public class Test1 implements Test{    public void prinTest() {        System.out.println("测试1");    }}public class Test2 implements Test{    public void prinTest() {        System.out.println("测试2");    }}interface NewTest {    public Test createrTest();}public class NewTest1 implements NewTest {    public Test createrTest() {         return new Test1();    }}public class NewTest2 {    public Test createrTest() {         return new Test2();    }}public class Client {    public static void main(String[] args)    {        NewTest nw = new NewTest1();        Test t1 = nw.createrTest();        t1.prinTest();    }}

以后我们想增加一个产品的时候,只需要增加该产品类和该工厂类,不需要修改之前的代码,这样程序就符合了开闭原则


里氏代换原则



我喜欢花,这说明我什么花都喜欢,但是我喜欢牵牛花,不等于我喜欢所有的花。在面向对象中有这么一个原则,将一个基类引用指向子类对象,程序将不会产生错误和异常,如果将一个子类引用指向基类对象,那就很可能会出错。

例如,我们现在有一个Aniaml类,Dog和Cat两个子类都从Animal继承。当我们有一个Dog和Cat都通用的方法,使用的时候需要传入Dog或者Cat的对象时,我们就应该这样写。

method(Animal a){···}

反过来,如果我们这样写

method(Dog dog){···}

然后传入一个Animal对象,你说行不行?

由于使用基类对象的地方都可以使用子类对象,因此在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象。

父类可以代替子类,子类不能代替父类。


依赖倒转原则



这个原则概括起来就是抽象不依赖细节,细节要依赖抽象。实现的方式是针对接口编程,不要针对实现编程,它也是面向对象思想中一个降低耦合度的设计原则。

上面这些概念你一定看得很晕,我们来看一个例子。

public class ReadDate {    public void read(DocDate date)    {        System.out.println(date.date);    }}public class DocDate {    String date = "Doc数据";    public String getDate()    {        return date;    }}

我们定义了一个读取数据的类,也定义了一个操作Doc数据的类,我们能通过读取数据的类,来读取DocDate里关于Doc文档的内容。

但是某一天,由于工作的需要,我们需要读取一些Excel表格的数据,那我们就得针对整个ReadDate进行修改。那如果以后我们还需要读取一些数据库的呢?那又得进行一次修改,这违背了开闭原则。

所以,我们干脆创建一个接口——Date接口,Doc和Excel类都实现了这个接口,再把ReadDate里read方法的引用改成接口Date引用,这样还满足了里氏代换原则。

public interface Date {    String getDate();}public class ExcelDate implements Date{    String date = "Excel数据";    public String getDate()    {        return date;    }}public class DocDate implements Date{    String date = "Doc数据";    public String getDate()    {        return date;    }}public class ReadDate {    public void read(Date date)    {        System.out.println(date.getDate());    }}

这样一来,ReadDate类与Date接口之间就是依赖的关系,而ReadDate与DocDate、Excel的关系就不再是依赖关系,以后我们增加一些新的数据类型,就不会因为需要修改ReadDate而造成一定的风险了。


接口隔离原则



了解过接口的都知道,接口是比抽象类更抽象的“类”(实际上并不是类),在实现一个接口的时候,就需要实现接口中的所有方法。

但是有这么一种情况,如果我们一个接口很大,定义了许多的方法,但是有时候客户端并不需要用到这么多接口。

那么我们就要考虑,将一个大接口切割成许多的小接口,每一个接口应该承担一种相对独立的角色,不干不该干的事,该干的事都要干。

接口隔离原则的概念:使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口。

A方法里面有a、b、c···各种方法,我们另一个类实现了这个接口,但是我们并不需要里面的一部分方法。

这个时候就麻烦了,我们得去实现里面所有的方法,这不仅仅增加了代码量。还会破坏封装型,让“用户”看到不该看的东西。

接口不能太小,如果太小会导致系统中接口泛滥,不利于维护;接口也不能太大,太大的接口将违背接口隔离原则,灵活性较差,使用起来很不方便。


合成复用原则



尽量使用对象组合,而不是继承来达到复用的目的。

在面向对象设计中和实现中,我们应该更多的考虑组合/聚合的方式,而不是继承。

继承的确可以得到父类几乎所有的功能,但是使用继承,会增加程序的复杂度和维护难度。

使用组合/聚合为什么比使用继承好还有另一个解释。使用聚合/组合的时候,是将一个对象纳入到一个新对象里面,被纳入对象的内部对新对象是未知的。如果是继承,继承之后会暴露基类的实现细节。

换句话说,组合/聚合是暗箱复用,继承是白箱复用。


达米特原则



一个实体类应当尽可能的少与其他实体类产生影响

(1) 当前对象本身(this);

(2) 以参数形式传入到当前对象方法中的对象;

(3) 当前对象的成员对象;

(4) 如果当前对象的成员对象是一个集合,那么集合中的元素也都是朋友;

(5) 当前对象所创建的对象。

如果满足以上任何一个条件的对象,就是当前对象的“朋友”,一个对象只能直接的与朋友发生交互,不要与“陌生人”发生交互。

如果一个实体类与很多的实体类发生交互,那么整个程序的耦合度就会很高,如果其中一个点修改,其他点就得跟着动。这样的代码,通常是不合格的。

2017/10/18 22:59:16 @Autrhor:云都小生(Cloudking)

原创粉丝点击