如何写出高质量的代码(面向对象的七大设计原则)

最近终于又有空闲时间对一些知识点进行归纳整理了,高质量的代码相信是每一个研发人员 梦寐以求的,高质量的代码意味着代码整洁规范、可读性强、可维护性高、易于扩展、复用度高,几乎没有冗余代码,那么怎么产出高质量的代码呢,我想在开发工程中,遵循面向对象的七大设计原则是一个很实用的切入点,下边对七大设计原则分三个章节进行详细介绍一下,以便加深理解。

一、概述

    面向对象设计的七大设计原则是软件设计的基本准则,它们分别是开闭原则(OCP)、里氏替换原则(LSP)、依赖倒置原则(DIP)、接口隔离原则(ISP)、迪米特法则(LoD)、单一职责原则(SRP)和组合/聚合复用原则(CARP)。

二、设计原则的好处及意义

    七大设计原则是软件开发中十分重要的指导原则,它们可以帮助开发者设计出更加灵活的软件系统,它们可以提高软件系统的质量、可维护性、可扩展性、可重用性,从而减少开发人员的工作量和系统维护成本。此外,这些原则还可以帮助开发人员编写出更加健壮和高效的代码,提高系统的可靠性和性能。

三、设计原则的详细阐述

    下边对这七大设计原则进行详细阐述,并用C++语言并结合设计模式进行举例说明:

  • 开闭原则(Open-Closed Principle,OCP)是面向对象设计中的一个重要原则,它要求软件系统应该对扩展开放,对修改关闭。这意味着当我们需要添加新功能时,应该通过扩展现有的代码来实现,而不是修改原有的代码。

          下面我们结合设计模式来举例说明开闭原则的应用。以工厂模式为例:

          在工厂模式中,我们可以定义一个抽象工厂类来描述创建对象的方法,然后派生出多个具体的工厂类来创建不同类型的对象。这种方法符合开闭原则,因为我们可以在不修改原有代码的情况下,通过添加新的具体工厂类来创建新的对象类型。

    以下是一个用C++实现的工厂模式的示例代码:

           在上面的示例中,我们定义了一个抽象工厂类AbstractFactory,其中有一个创建产品的纯虚函数createProduct()。我们派生出了两个具体工厂类ConcreteFactory1和ConcreteFactory2,它们分别创建了ConcreteProduct1和ConcreteProduct2两个具体产品类。在客户端代码中,我们可以通过创建不同的具体工厂类来创建不同类型的产品,而不需要修改原有的代码。

          通过使用工厂模式,我们可以轻松地添加新的具体工厂类和具体产品类,而不会影响原有的代码。这正是开闭原则的体现,它使得系统更加灵活、可扩展和可维护。

  •  里式替换原则(Liskov Substitution Principle,LSP)是指“所有引用基类(父类)的地方必须能透明地使用其子类的对象”,即任何基类可以出现的地方,子类一定可以出现,且替换成子类也不会产生任何错误或异常,保证了代码的健壮性和可扩展性。

          下面我们以一个简单的图形类为例,来说明里式替换原则在软件设计中的应用:

    在上面的代码中,Shape 是图形的基类,Circle 和 Square 是继承自 Shape 的子类,它们分别代表圆形和正方形。drawAllShapes 函数接受一个 Shape 对象指针的容器,用来绘制所有不同的图形。

      按照里式替换原则的要求,我们可以将 Shape 类型的指针替换为 Circle 或 Square 类型的指针,而不会影响程序的正确性,这也保证了代码的可扩展性。比如,我们可以定义一个新的图形类 Triangle:

并将它添加到 shapes 容器中:

      这样,在调用 drawAllShapes 函数时,Triangle 类型的对象也能被正确地绘制出来,这就是里式替换原则的好处之一。

      另外,当我们在设计软件时,也应该注意里式替换原则,即在设计子类时,必须确保子类的行为和父类的行为一致,或者说,子类可以替换掉父类,并且不会产生任何异常或错误。这样才能使得软件具有良好的可维护性、可扩展性和可重用性。

  • 接口隔离原则(Interface Segregation Principle, ISP)是指一个类不应该依赖于它不需要的接口,也就是说,一个类对另一个类的依赖应该建立在最小的接口上。

          举个例子,假设我们正在开发一个图形编辑器,其中有多种形状(Shape),如矩形、圆形、三角形等等。每个形状都有一个共同的特点,就是可以被选择(select),可以被移动(move),也可以被缩放(resize)。我们可以先定义一个Shape接口,包含这三个方法:

          然后定义几个具体的形状,如矩形(Rectangle)、圆形(Circle)等,它们继承自Shape接口,并实现对应的方法。例如,Rectangle类可以这样实现:

         此时,我们发现所有形状都必须实现Shape接口中的三个方法,但并不是所有形状都需要这三个方法。例如,线段(Line)形状不能被缩放,如果强制让Line类实现resize方法,就会产生代码冗余和接口污染的问题。

          为了遵守接口隔离原则,我们可以把Shape接口拆分成更小的接口,让每个具体的形状只依赖于自己需要的接口。例如,我们可以把Shape接口拆分成Selectable、Movable和Resizable三个接口:

          然后,让每个形状只继承需要的接口。例如,Rectangle类只继承Selectable、Movable和Resizable三个接口,而Line类只继承Selectable和Movable两个接口:

  •  依赖倒置原则(Dependency Inversion Principle,DIP)是面向对象设计中的一个重要原则,其核心思想是:高层模块不应该依赖低层模块,它们应该依赖于抽象接口。同时,抽象不应该依赖于细节,细节应该依赖于抽象。这种设计原则可以提高代码的灵活性、可扩展性和可维护性。

          一个常用的实现依赖倒置原则的方法是通过依赖注入(Dependency Injection,DI)来实现。依赖注入是指在创建对象时,通过构造函数或者方法将依赖对象传入对象中,从而实现依赖关系的解耦。下面用一个简单的例子来说明依赖倒置原则和依赖注入的实现。

          假设我们要实现一个计算器程序,可以进行加、减、乘、除四种运算。首先我们定义一个接口Calculator,包含一个计算方法calculate:

          然后,我们针对加、减、乘、除四种运算,各自实现一个具体的计算类Add, Subtract, Multiply, Divide,并分别实现Calculator接口中的计算方法:

          接下来,我们需要一个计算器类CalculatorApp,用于进行各种计算。在传统的设计中,CalculatorApp直接依赖于四种具体的计算类,这样就违反了依赖倒置原则。我们需要将CalculatorApp依赖于Calculator接口,从而将具体实现类的依赖转移到了高层模块之外。代码如下:

          现在,我们可以通过依赖注入的方式将具体实现类注入到CalculatorApp中。例如,我们可以创建一个Add对象并将其注入到CalculatorApp中:

  • 单一职责原则(Single Responsibility Principle,SRP)要求一个类只有一个责任,即一个类只有一种引起它变化的原因。如果一个类承担了多个职责,就会造成耦合度增加、复用性降低等问题。这里举例说明单一职责原则的应用,结合设计模式中的代理模式。

          代理模式是一种结构型设计模式,用于为其他对象提供一种代理以控制对这个对象的访问。代理模式常常用于在访问对象时增加一些额外的功能,比如在访问远程对象时进行网络连接、认证和授权等操作。在代理模式中,我们通常将代理类作为一个封装类,用于将被代理对象的某些职责划分到代理类中,从而保持被代理对象的单一职责。

          下面是一个简单的例子,展示代理模式如何遵循单一职责原则:

          在这个例子中,Subject 是抽象主题类,RealSubject 是真实主题类,Proxy 是代理类。在代理模式中,Proxy 承担了部分 RealSubject 的职责,并在请求前后进行了额外的操作,但仍然保持了 RealSubject 的单一职责。

          通过代理模式的实现,我们可以把 RealSubject 中的请求操作职责分离到了 Proxy 中,从而让 RealSubject 专注于自身的业务逻辑。这样做可以提高代码的复用性、可维护性和可扩展性,同时也更好地遵循了单一职责原则。

  •  迪米特法则(也叫最少知识原则)主要强调在对象之间的交互中,应当尽量减少对象之间的耦合,一个对象应该尽量少的知道其他对象的信息,避免直接依赖其他对象,从而降低系统的耦合度。

          迪米特法则可以通过使用中介者模式来实现,中介者模式主要用于降低系统中对象之间的直接耦合,通过引入一个中介者对象来协调对象之间的交互。下面以一个简单的例子来说明:

          假设我们有一个游戏角色类和一个游戏角色管理器类,其中游戏角色管理器类负责管理游戏角色的创建、销毁等操作。如果游戏角色类直接依赖游戏角色管理器类,则会使得游戏角色类和游戏角色管理器类紧密耦合,不利于系统的扩展和维护。

          为了避免这种紧密耦合,我们可以引入一个游戏角色工厂类作为中介者,游戏角色类只与游戏角色工厂类进行交互,而不是直接依赖游戏角色管理器类。游戏角色工厂类负责管理游戏角色的创建、销毁等操作,从而实现游戏角色类和游戏角色管理器类的解耦。

    下面是相应的代码实现:

          在上面的代码中,IGameCharacter是游戏角色接口,GameCharacter是游戏角色实现类,IGameCharacterFactory是游戏角色工厂接口,GameCharacterFactory是游戏角色工厂实现类,GameCharacterManager是游戏角色管理器类。在GameCharacterManager中,我们使用了游戏角色工厂类作为中介者,游戏角色类只与游戏角色工厂类进行交互。

  • 合成复用原则(Composite Reuse Principle,CRP)是指尽量使用对象组合,而不是通过继承来达到复用的目的。合成复用原则可以让类具有更好的可维护性和扩展性,同时减少类与类之间的耦合度。下面通过一个示例来说明合成复用原则的应用。

          一个常见的例子是在设计GUI(图形用户界面)时,使用合成复用原则可以更好地管理控件和布局。在这个例子中,可以使用一个窗体类来代表整个界面,而窗体类包含一个由各种控件(例如按钮、标签、文本框等)组成的布局。每个控件都是通过组合方式添加到窗体类中,而不是将每个控件作为窗体类的子类进行继承。

           举个具体的例子,在一个文本编辑器中,窗体类可以代表整个编辑器的主窗口,而各种控件可以代表编辑器中的各种元素,例如编辑区、工具栏、状态栏等等。这些控件可以通过合成的方式添加到窗体类中,以实现整个编辑器的布局和功能。如果使用继承的方式来实现每个控件,将会导致代码的复杂性和可维护性降低,因为每个子类都需要维护自己的状态和行为,而这些状态和行为通常都是相互关联的。而使用合成复用原则可以更好地将控件的状态和行为分离,并将它们封装在一个独立的对象中,以便更好地管理和维护。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

田翁野老

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值