如何提高代码的扩展性(3)

简介: 如何提高代码的扩展性(3)

2、如何实现代码的高质量?


遵循SOLID设计原则:(接口设计原则)参考依据高内聚、低耦合


单一职责原则:一个类值负责一个功能的职责

开闭原则:扩展开放,修改关闭。

里氏代换原则:使用父类的地方都能使用子类对象

依赖倒转原则:针对接口编程,

接口隔离原则:针对不同部分用专门接口,不用总接口,需要哪些接口就用哪些接口


image.png


你认为下图的设计违反了哪一种设计原则?


image.png


(1)违反了单一职责原则(SRP)


画图和计算面积并不是单一职责,计算几何学应用程序只计算面积不画图,但是还要引入GUI。


应该有且仅有一个原因引起类的变更。简单点说,一个类,最好只负责一件事,只有一个引起它变化的原因。也就是说引起类变化的原因只有一个。高内聚、低耦合是软件设计追求的目标,而单一职责原则可以看做是高内聚、低耦合的引申,将职责定义为引起变化的原因,以提高内聚性,以此来减少引起变化的原因。职责过多,可能引起变化的原因就越多,这将是导致职责依赖,相互之间就产生影响,从而极大的损伤其内聚性和耦合度。单一职责通常意味着单一的功能,因此不要为类实现过多的功能点,以保证实体只有一个引起它变化的原因。


(2)开闭原则


对扩展开放。模块对扩展开放,就意味着需求变化时,可以对模块扩展,使其具有满足那些改变的新行为。换句话说,模块通过扩展的方式去应对需求的变化。


对修改关闭。模块对修改关闭,表示当需求变化时,关闭对模块源代码的修改,当然这里的“关闭”应该是尽可能不修改的意思,也就是说,应该尽量在不修改源代码的基础上面扩展组件。


一个开闭原则的简单实例(懂则不用看)



image.png


  对拓展开放,对修改关闭:比如当某个业务增加,不是在原类增加方法,而是增加原类的实现类。


下面的例子是一个非常典型的开闭原则及其实现。非常简单,但却能够很好的说明开闭原则。


假设有一个应用程序,能够计算任意形状面积。这是几年前我在明尼苏达州农作物保险公司遇到的一个非常简单问题。app程序必须能够计算出指定区域的农作物总的保险报价。正如你所知道的,农作物有各种形状和大小,有可能是圆的,有可能是三角形的也可能是其他各种多边形。


OK,让我们回到我们之前的例子中....


作为一名优秀的程序员,我们将这个面积计算类命名为 AreaManager。这个 AreaManager是单一职责的类:计算形状的总面积 。


假设我们现在有一块矩形的农作物,我omen用一个Rectangle类来表示。相关类代码如下:


public class Rectangle {
    private double length;
    private double height; 
    // getters/setters ... 
}
public class AreaManager {
    public double calculateArea(ArrayList<Rectangle>... shapes) {
        double area = 0;
        for (Rectangle rect : shapes) {
            area += (rect.getLength() * rect.getHeight()); 
        }
        return area;
    }
}
AreaManager类现在运行良好,直到几周之后,我们又有一种新的形状——圆形:
public class Circle {
    private double radius; 
    // getters/setters ...
}
由于有新的形状需要考虑,我们必须修改我们的AreaManager类:
public class AreaManager {
    public double calculateArea(ArrayList<Object>... shapes) {
        double area = 0;
        for (Object shape : shapes) {
            if (shape instanceof Rectangle) {
                Rectangle rect = (Rectangle)shape;
                area += (rect.getLength() * rect.getHeight());                
            } else if (shape instanceof Circle) {
                Circle circle = (Circle)shape;
                area += (circle.getRadius() * cirlce.getRadius() * Math.PI;
            } else {
                throw new RuntimeException("Shape not supported");
            }            
        }
        return area;
    }
}
从这段代码开始,我们察觉到了问题。
如果我们遇到一个三角形,或者其他形状呢,这时候我们就必须一次又一次的修改AreaManager类。
这个类的设计就违背了开闭原则,没有做到对修改的封闭性以及对扩展的开放性。我们必须避免这种事情的发生~
基于继承的开闭原则的实现
AreaManager类的职责是计算各种形状的面积,而每一种形状都有其独特的计算面积的方法,因此将面积的计算放入到各个形状类中是特别合理的。
AreaManager类仍然需要知道所有的形状,否则它就无法判断所有的形状类是否都包含了计算面积的方法。当然了,我们可以通过反射来实现。其实有一种更简单的方式也可以实现——让所有的形状类都继承一个接口:Shape(也可以是抽象类)
public interface Shape {
    double getArea(); 
}
每一个形状类都实现这个接口(如果接口无法满足你的需求,也可以通过继承某个抽象类):
public class Rectangle implements Shape {
   private double length;
   private double height; 
   // getters/setters ... 
   @Override
   public double getArea() {
       return (length * height);
   }
}
public class Circle implements Shape {
   private double radius; 
   // getters/setters ...
   @Override
   public double getArea() {
       return (radius * radius * Math.PI);
   }
}
现在,我们可以通过这个抽象方法将AreaManager构造成一个符合开闭原则的类。
public class AreaManager {
    public double calculateArea(ArrayList<Shape> shapes) {
        double area = 0;
        for (Shape shape : shapes) {
            area += shape.getArea();
        }
        return area;
    }
}


通过扩展去应对需求变化,就要求我们必须要面向接口编程,或者说面向抽象编程。所有参数类型、引用传递的对象必须使用抽象(接口或者抽象类)的方式定义,不能使用实现类的方式定义;通过抽象去界定扩展,比如我们定义了一个接口A的参数,那么我们的扩展只能是接口A的实现类。总的来说,开闭原则提高系统的可维护性和代码的重用性。

相关文章
|
5月前
|
消息中间件 存储 负载均衡
|
5月前
|
算法 Linux C++
C++框架设计中实现可扩展性的方法
在软件开发中,可扩展性至关重要,尤其对于C++这样的静态类型语言。本文探讨了在C++框架设计中实现可扩展性的方法:1) 模块化设计降低耦合;2) 使用继承和接口实现功能扩展;3) 通过插件机制动态添加功能;4) 利用模板和泛型提升代码复用;5) 遵循设计原则和最佳实践;6) 应用配置和策略模式以改变运行时行为;7) 使用工厂和抽象工厂模式创建可扩展的对象;8) 实现依赖注入增强灵活性。这些策略有助于构建适应变化、易于维护的C++框架。
359 2
|
5月前
|
中间件 微服务 缓存
中间件性能和可扩展性
【6月更文挑战第1天】
69 9
|
6月前
|
存储 分布式计算 负载均衡
HadoopHDFS的特点可扩展性
【5月更文挑战第11天】HadoopHDFS的特点可扩展性
125 1
|
6月前
|
监控 中间件
选择中间件性能和可扩展性
【5月更文挑战第20天】
64 1
|
5月前
|
敏捷开发 存储 缓存
【DDIA笔记】【ch1】 可靠性、可扩展性和可维护性 -- 可维护性
【6月更文挑战第4天】本文探讨了Twitter面临的一次发推文引发的巨大写入压力问题,指出用户粉丝数分布是决定系统扩展性的关键因素。为解决此问题,Twitter采用混合策略,大部分用户推文扇出至粉丝主页时间线,而少数名人推文则单独处理。性能指标包括吞吐量、响应时间和延迟,其中高百分位响应时间对用户体验至关重要。应对负载的方法分为纵向和横向扩展,以及自动和手动调整。文章强调了可维护性的重要性,包括可操作性、简单性和可演化性,以减轻维护负担和适应变化。此外,良好设计应减少复杂性,提供预测性行为,并支持未来改动。
56 0
|
5月前
|
缓存 关系型数据库 数据库
【DDIA笔记】【ch1】 可靠性、可扩展性和可维护性 -- 可扩展性
【6月更文挑战第3天】可扩展性关乎系统应对负载增长的能力,但在产品初期过度设计可能导致失败。理解基本概念以应对可能的负载增长是必要的。衡量负载的关键指标包括日活、请求频率、数据库读写比例等。推特的扩展性挑战在于&quot;扇出&quot;,即用户关注网络的广度。两种策略包括拉取(按需查询数据库)和推送(预计算feed流)。推送方法在推特案例中更为有效,因为它减少了高流量时的实时计算压力。
52 0
|
6月前
模块功能复用和扩展性
模块功能复用和扩展性 模块功能复用和扩展性是软件工程中的重要概念,主要体现在设计和实现阶段。
85 1
|
设计模式 算法
如何优雅地使用策略模式来实现更灵活、可扩展和易于维护的代码?
如何优雅地使用策略模式来实现更灵活、可扩展和易于维护的代码?
88 0
如何提高代码的扩展性(4)
如何提高代码的扩展性(4)
130 0
如何提高代码的扩展性(4)