面向对象设计原则--开闭原则(OCP)

面向对象设计原则–开闭原则(OCP)

tags:设计模式

OCP–Closed for Modification;Open for Extension.

原则概述

开闭原则要求软件有一个良好的基本结构,确保面对变化的时候,仅仅扩展而不是修改现有对象的组织框架就可以随需而动.但我们知道越是这样趋于道可道非常道的内容就越难以找到一个固定的方式达成,我们可以举出很多例子指出这样或那样是违反开闭原则的,但不容易展示怎样才是开闭原则,原因在于影响软件变化的原因很多,即便现有的对象组织框架针对现状基本上满足了扩展要求,未来也常常发现框架本身成了事件开闭原则的阻力.

原则示例

我们把之前讲的DataHandler的例子进一步具体化,以讨论如何设计更符合开闭原则的要求.

我们的DataHandler是一个简易的ETL工具,即数据的抽取,装载,转换,它只负责提取整个表或视图的数据.

这个工具深受好评,公司决定将这个工具作为主打产品,但问题是数据库产品总是在增加,数据库产品版本总在升级,最近NO-SQL运行兴起后又出现了一大批非关系型的新型数据库.

第一版设计

在第一版是产品只支持oracle数据库和sql server数据库:

class Etx{
    ConnectionManager cm;
    List<DataRow> getDataObject(dbName,name)
    type = cm.getDbType(dbName);
    if(type == ORACLE)
    {
        db = new OracleDataBase();
        return  db.getObject(name);
    }
    else if(type = SQL Server)
    {
        db = new SQLserverDataBase();
        return db.getObject(name);
    }else
    {
        throw new ClassNotFoundException(type);    
    }
}
class ConnectionManager
{
    String getDbType(name)
}

第一版设计分析

ETL类负责提取,ConnectionManager类管理数据库连接,两个类各司其职,而且根据上面示例情景的假设,影响ETL变化的因素只有一个–新类型的数据库,所以这个设计基本上满足单一职责原则的要求.

没有满足里氏替换原则和依赖倒置原则的要求.

两个接口类简单,仅从方法数量上看,各自简单到对外只有一个公共方法,基本符合接口隔离原则.

迪米特法则的各层意思基本上都能满足.

虽然满足了以上一些准则但是这个类有很大的缺陷,就是因为一旦有新的数据库,ETL类就要修改,而不是扩展,于是我们又重构了第二版

第二版设计

伪码如下:

class interface DatabaseEtlEntity
{
    List<DataRow> getObjectByName(name);
}
class Etl
{
    List<DataRow> getObjectByName(name)
    {
        return ConnectionManager
        .getDatabaseEtlEntity(dbName);
    }
}
class ConnectionManager
{
    public DatabaseEtlEntity getDatabaseEtlEntity(dbName)
    {
        switch(getDbType(dbName))
        {
            case ORACLE:
                return new OracleDbAdapter();
            case SQL Server:
                return new SqlDbAdapter();
            default:
                throw new ClassNotFoundException();
        }
    }
    private String getDbType(dbName)
    {
        return ...
    }
}
class SqlDbAdapter{}
class OracleDbAdapter{}

第二版设计分析

上面的设计在保留了之前的一些特点外,还通过里氏替换原则和依赖倒置原则实现支持多数据库的目标,而且ConnectionManager的getDbType()方法被置为private方法,所以传承了接口隔离原则.

但我们也注意到,尽管ETL类在当前语境设计下满足开闭原则要求了,但却把麻烦抛给了ConnectionManager类了,因为以后如果要增加MYSQL DB2这些数据库的时候,ConnectionManager类还要继续修改.由于修改集中在构造过程,既然应用自己来解决问题总是难免造成与具体类型的依赖关系(ConnectionManager类对于SqlDbAdapter的依赖性),不妨考虑将变化转移到语言运行环境层面.构造对象时,java平台可以使用Class.newInstance()方法.

置于数据库具体该用什么对象来处理,这些信息也找一个相对稳定的机制–配置文件保存即可.

第三版设计

class interface DatabaseEtlEntity
{
    List<DataRow> getObjectByName(name);
}
class Etl
{
    List<DataRow> getDataObject(dbName,name)
    {
        return ConnectionManager.
            getDatabaseEtlEntity(dbName).
            getObjectByName(name);
    }
}
class ConnectionManager
{
    DatabaseEtlEntity getDatabaseEtlEntity(dbName)
    {
        typeName = ConfigManager.getType(dbName);
        return Class.newInstance(typeName);
    }
}
class ConfigManager
{
    string getConfig(sectionName)
    {
        read...config-file;
    }
}

第三版设计分析

第三版设计在当前语境下也大致满足了开闭原则的要求,但代价是增加了一系列对象,是在用现在的工作量减轻未来可能出现的工作量,投入是明显的,收入却是不确定的.实际项目中,面对开闭原则我们除了技术上这种敝帚自珍外还要考虑这么做是否值得,我们是不是把真的80%的精力放到那20%关键对象部分,而不是抛开软件需要不论,过度打磨自己的设计.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值