定义

一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。

开闭原则强调的是用抽象构建框架,用实现来扩展细节。

示例

这里以超市中的商品为例

创建商品接口

首先创建一个商品接口,同时定义获取商品ID、名称、价格的方法。一般来说,一个商品是一个实体,我们可以去写一个类包含商品ID、名称、价格这三个成员变量,这里仅仅为了演示开闭原则。

/**
 * @author zhh
 * @description 商品接口
 * @date 2019-07-23 23:46
 */
public interface IGoods {

    /**
     * 获取商品ID
     */
    Integer getId();

    /**
     * 获取商品名称
     */
    String getName();

    /**
     * 获取商品价格
     */
    Double getPrice();
}

创建商品子类

我们的商品其实是有很多分类的,比如食品、用品、电器等等。这里以食品为例,创建食品类同时实现商品接口。

/**
 * @author zhh
 * @description 食品类
 * @date 2019-07-24 00:21
 */
public class Food implements IGoods {

    private Integer id;

    private String name;

    private Double price;

    public Food(Integer id, String name, Double price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }

    public Integer getId() {
        return this.id;
    }

    public String getName() {
        return this.name;
    }

    public Double getPrice() {
        return this.price;
    }
}

测试类及输出

/**
 * @author zhh
 * @description 测试类
 * @date 2019-07-23 23:46
 */
public class Test {

    public static void main(String[] args) {
        IGoods iGoods = new Food(1, "巧克力", 9.9);

        String msg = String.format("商品ID: %s, 商品名称: %s, 商品价格: %s",
                iGoods.getId(), iGoods.getName(), iGoods.getPrice());

        System.out.println(msg);
    }
}

商品ID: 1, 商品名称: 巧克力, 商品价格: 9.9

类结构图

目前的类结构图如下所示

需求调整

假设现在超市进行促销活动,全场食品类商品打6折,那我们如何来实现这个需求?

改动

针对上述例子我们也许会这样做:

  • 在商品接口中新定义一个获取商品折扣价的方法
public interface IGoods {
    ...
    
    /**
     * 获取商品折扣价格
     */
    Double getDiscountPrice();
}
  • 具体食品类实现该方法
public class Food implements IGoods {
	...
        
    public Double getDiscountPrice() {
        return getPrice() * 0.6;
    }
}

不足

以上改动看样子确实满足了我们的需求,但是这种改动不仅修改了接口,对具体的实体类也进行了改动。

  • 假设我们的商品分类有很多,那么所有的商品实现类都需要去实现接口新增的方法。
  • 接口作为一种契约,是不应该经常变化的,它应该是稳定且可靠的。

新的思路

改动

通过扩展新建一个食品的子类用来处理食品类打折,覆写食品类获取价格的方法,同时提供一个获取商品原价的方法

/**
 * @author zhh
 * @description 食品打折类
 * @date 2019-07-24 01:09
 */
public class FoodDiscount extends Food {

    public FoodDiscount(Integer id, String name, Double price) {
        super(id, name, price);
    }

    public Double getOriginPrice() {
        return super.getPrice();
    }

    @Override
    public Double getPrice() {
        return this.getOriginPrice() * 0.6;
    }
}

测试类及输出

/**
 * @author zhh
 * @description 测试类
 * @date 2019-07-23 23:46
 */
public class Test {

    public static void main(String[] args) {
        IGoods iGoods = new FoodDiscount(1, "巧克力", 9.9);

        FoodDiscount foodDiscount = (FoodDiscount) iGoods;

        String msg = String.format("商品ID: %s, 商品名称: %s, 商品原价: %s, 商品折后价: %s",
                foodDiscount.getId(), foodDiscount.getName(), foodDiscount.getOriginPrice(), foodDiscount.getPrice());

        System.out.println(msg);
    }
}

商品ID: 1, 商品名称: 巧克力, 商品原价: 9.9, 商品折后价: 5.94

类结构图

好处

不修改底层的基类和接口,防止风险的扩散。

通过继承基类,使得对扩展开放,对于修改接口和基类是关闭的。

优点

提高软件系统的可复用性和可维护性