开闭原则(开放封闭原则)

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

        开闭原则的定义已经非常明确地告诉我们:软件实体应该对扩展开发,对修改关闭,其含义是说一个软件实体应该通过扩展来实现变化的,而不是通过修改来实现变化的,那什么又是软件实体?

        软件实体包括以下几个部分:

  • 项目或软件产品中按照一定的逻辑规则划分的模块。
  • 抽象和类。
  • 方法。

        一个软件的存在,都会发生变化的,既然变化是一个不变的事实,我们就应该在设计时尽量适应这些变化,从而提高项目的稳定性和灵活性,真正实现“拥抱变化”。开闭原则告诉我们应尽量通过扩展软件实体的行为来实现变化, 而不是通过修改已有的代码来完成变化, 它是为软件实体的未来事件而制定的对现行开发设计进行约束的一个原则。 我们举例说明什么是开闭原则, 以书店销售书籍为例,如图1-1所示。

图1-1

                                不明白的这个图画的意思,可以看一下这个文章 :文章

        IBook定义了数据的三个属性: 名称、 价格和作者。 小说类NovelBook是一个具体的实现
类, 是所有小说书籍的总称, BookStore指的是书店, IBook接口如代码清单2-1所示。

//代码清单2-1 书籍接口
public interface IBook {
    //书籍有名称
    public String getName();
    //书籍有售价
    public int getPrice();
    //书籍有作者
    public String getAuthor();
}

        目前书店只出售小说类书籍, 小说类如代码清单2-2所示。

//代码清单2-2 小说类。
public class NovelBook implements IBook {
    //书籍名称
    private String name;
    //书籍的价格
    private int price;
    //书籍的作者
    private String author;
    //通过构造函数传递书籍数据
    public NovelBook(String _name,int _price,String _author){
        this.name = _name;
        this.price = _price;
        this.author = _author;
    }/
    /获得作者是谁
    public String getAuthor() {
        return this.author;
    }/
    /书籍叫什么名字
    public String getName() {
        return this.name;
    }/
    /获得书籍的价格
    public int getPrice() {
        return this.price;
    }
}

        书店售书的过程如代码清单2-3所示。

        注意 我们把价格定义为int类型并不是错误, 在非金融类项目中对货币处理时, 一般取
2位精度, 通常的设计方法是在运算过程中扩大100倍, 在需要展示时再缩小100倍, 减少精
度带来的误差。

//代码清单2-3所示
public class BookStore {
    private final static ArrayList<IBook> bookList = new ArrayList<IBook>();
    //static静态模块初始化数据, 实际项目中一般是由持久层完成
    static{
        bookList.add(new NovelBook("天龙八部",3200,"金庸"));
        bookList.add(new NovelBook("巴黎圣母院",5600,"雨果"));
        bookList.add(new NovelBook("悲惨世界",3500,"雨果"));
        bookList.add(new NovelBook("金瓶梅",4300,"兰陵笑笑生"));
    }

    //模拟书店买书
    public static void main(String[] args) {
        NumberFormat formatter = NumberFormat.getCurrencyInstance();
        formatter.setMaximumFractionDigits(2);
        System.out.println("-----------书店卖出去的书籍记录如下: -----------");
        for(IBook book:bookList){
            System.out.println("书籍名称: " + book.getName()+"\t书籍作者: "
            book.getAuthor()+"\t书籍价格: "+ formatter.format(
            book.getPrice()/100.0)+"元");
        }
    }
   
}

        在BookStore中声明了一个静态模块, 实现了数据的初始化,运行结果如下:

-----------------书店卖出去的书籍记录如下: --------------
书籍名称: 天龙八部 书籍作者: 金庸 书籍价格: ¥32.00元
书籍名称: 巴黎圣母院 书籍作者: 雨果 书籍价格: ¥56.00元
书籍名称: 悲惨世界 书籍作者: 雨果 书籍价格: ¥35.00元
书籍名称: 金瓶梅 书籍作者: 兰陵笑笑生 书籍价格: ¥43.00元

        现在项目已经完成了,这个时候,产品经理提出:所有40元以上的书籍9折销售, 其他的8
折销售。 对已经投产的项目来说, 这就是一个变化,那我们如何对应这次变化,有三种方法可以已经这个问题:

  • 修改接口:

        在IBook上新增加一个方法getOffPrice(), 专门用于进行打折处理, 所有的实现类实现该方法。 但是这样修改的后果就是, 实现类NovelBook要修改, BookStore中的main方法也修改, 同时IBook作为接口应该是稳定且可靠的, 不应该经常发生变化, 否则接口作为契约的作用就失去了效能。 因此, 该方案肯定不行的。

  • 修改实现类:

        修改NovelBook类中的方法, 直接在getPrice()中实现打折处理,或者通过动态替换class文件的方式来完成,显然也是可以实现的,但是这个方案有个比较明显的错误,例如我们修改这个getPrice()这个方法,那么这个书籍的原价格我们就看不到了,这个方案也是行不通的。

  • 通过扩展实现变化

        增加一个子类OffNovelBook, 覆写getPrice方法,修改高层次的模块(就是static静态模块初始化数据)通过OffNovelBook类产生新的对象,就可以完成这次打折的功能,并没有修改接口,或者修改原有的类,  static静态模块该部分属于高层次的模块, 是由持久层产生的, 在业务规则改变的情况下高层模块必须有部分改变以适应新业务, 改变要尽量地少, 防止变化风险的扩散。

图1-2,扩展后的书店售书类图

                OffNovelBook类继承了NovelBook, 并覆写了getPrice方法, 不修改原有的代码。 新增加的子类OffNovelBook如代码清单2-4所示。

//代码清单2-4所示
public class OffNovelBook extends NovelBook {
    public OffNovelBook(String _name,int _price,String _author){
        super(_name,_price,_author);
    }//覆写销售价格
    @Override
    public int getPrice(){
        //原价
        int selfPrice = super.getPrice();
        int offPrice=0;
        if(selfPrice>4000){ //原价大于40元, 则打9折
            offPrice = selfPrice * 90 /100;
        }else{
            offPrice = selfPrice * 80 /100;
        }
        return offPrice;
    }
}

        很简单, 仅仅覆写了getPrice方法, 通过扩展完成了新增加的业务。 书店类BookStore需
要依赖子类, 代码稍作修改, 如代码清单2-5所示。

//代码清单2-5所示
public class BookStore {
    private final static ArrayList<IBook> bookList = new ArrayList<IBook>();
    //static静态模块初始化数据, 实际项目中一般是由持久层完成
    static{
        bookList.add(new OffNovelBook("天龙八部",3200,"金庸"));
        bookList.add(new OffNovelBook("巴黎圣母院",5600,"雨果"));
        bookList.add(new OffNovelBook("悲惨世界",3500,"雨果"));
        bookList.add(new OffNovelBook("金瓶梅",4300,"兰陵笑笑生"));
    }//模拟书店买书
    public static void main(String[] args) {
        NumberFormat formatter = NumberFormat.getCurrencyInstance();
        formatter.setMaximumFractionDigits(2);
        System.out.println("-----------书店卖出去的书籍记录如下: -----------");
        for(IBook book:bookList){
            System.out.println("书籍名称: " + book.getName()+"\t书籍作者: "
            book.getAuthor()+"\t书籍价格: "+ formatter.format(
            book.getPrice()/100.0)+"元");
        }
    }
}

         我们只是修改了static静态模块,其他的地方没有动.

-----------------书店卖出去的书籍记录如下: --------------
书籍名称: 天龙八部 书籍作者: 金庸 书籍价格: ¥25.60元
书籍名称: 巴黎圣母院 书籍作者: 雨果 书籍价格: ¥50.40元
书籍名称: 悲惨世界 书籍作者: 雨果 书籍价格: ¥28.00元
书籍名称: 金瓶梅 书籍作者: 兰陵笑笑生 书籍价格: ¥38.70元

        OK,

参考文档来自:设计模式之禅。
 

猜你喜欢

转载自blog.csdn.net/fry3309/article/details/123582415