为了账号安全,请及时绑定邮箱和手机立即绑定

面向对象4原则

标签:
Java
OCP(开闭原则)

类应该对扩展开放,对修改而关闭。

应用举例

本人是做彩票业务的,就以彩票举例吧。下面是一段设计不良的校验投注号码的代码

 public boolean validate(String drawNum){
    if (type.equals("PL3")) {  
        PL3Validate validatePL3 = new PL3Validate();  
        validatePL3.validate();  
    }  
    else if (type.equals("PL5")) {  
        PL5Validate validatePL5 = new PL5Validate();  
        validatePL5.validate();  
    } 
}

其对应的类图为:

image

若这时添加大乐透彩种的校验,需要修改OCPDemo中的validate的代码,加入另外一个else if 分支,这违反了OCP原则,并没有对修改而关闭。
可以进行如下修改:
我们添加抽象类AbstractNumberValidate,让PL3Validate和PL5Validate继承该类,OCPDemo仅依赖AbstractNumberValidate类。上面的代码修改为:

 AbstractNumberValidate validate;
 public static class PL3ValidateImpl extends AbstractNumberValidate{
     public boolean validate(String drawNum){
         return false;
     }
 }

修改后的类图为:

image

这样无论添加任何彩种,OCPDemo的validate都不需要更改。若这时添加大乐透彩种的校验,只需要添加一个DLTValidate类继承AbstractNumberValidate实现自己的校验规则,并注入到OCPDemo中即可。

这里仅仅以继承的方式来解决上边的问题,解法不唯一。

OCP不仅仅是继承

OCP关系到灵活性,而不只是继承。
例如:你在类中有一些private的方法,(这就是禁止为修改而关闭),但是你有一些public方法以不同的方式调用private方法(允许为扩展而开放)

OCP的核心是 让你有效的扩展程序,而不是改变之前的程序代码。

DRY(不自我重复)

通过将共同之物抽取出来并置于单一地方避免重复的程序代码。

举例说明

Java初学者,使用JDBC,查询数据库中数据时,会有如下代码,每调用一个查询均会有
3部分,执行查询,提取结果,关闭结果集合。

        //调用查询
        stmt = conn.createStatement();
        result = stmt.executeQuery("select * from person");//执行sql语句,结果集放在result中  
        //提取结果
        while(result.next()){//判断是否还有下一行  
            String name = result.getString("name");//获取数据库person表中name字段的值  
            Person p=new Person();
            p.setName(name);
        }  
        //关闭结果集合
        result.close();  
        stmt.close();  

如果每调用查询一次数据库均要写上述代码,绝对会非常的累,也违反DRY原则,系统中会出现大量的重复代码。
下面让我们看看Spring的JdbcTemplate如何遵循DRY原则。上边的模式,有一定的套路,Spring总结了套路,封装成了模板,经过Spring的封装,只需传入Sql,和结果集合转换的类。代码如下:

    //实际只需调用queryForObject即可
    @Override
    public <T> T queryForObject(String sql, Class<T> requiredType) throws DataAccessException {
        return queryForObject(sql, getSingleColumnRowMapper(requiredType));
    }

    public <T> T query(final String sql, final ResultSetExtractor<T> rse) throws DataAccessException {
        Assert.notNull(sql, "SQL must not be null");
        Assert.notNull(rse, "ResultSetExtractor must not be null");
        if (logger.isDebugEnabled()) {
            logger.debug("Executing SQL query [" + sql + "]");
        }
        class QueryStatementCallback implements StatementCallback<T>, SqlProvider {
            @Override
            public T doInStatement(Statement stmt) throws SQLException {
                ResultSet rs = null;
                try {
                    //执行SQL
                    rs = stmt.executeQuery(sql);
                    //----提取结果-start
                    ResultSet rsToUse = rs;
                    if (nativeJdbcExtractor != null) {
                        rsToUse = nativeJdbcExtractor.getNativeResultSet(rs);
                    }
                    return rse.extractData(rsToUse);
                    //--------提取结果-end
                }
                finally {
                //关闭结果集合
                    JdbcUtils.closeResultSet(rs);
                }
            }
            @Override
            public String getSql() {
                return sql;
            }
        }
        return execute(new QueryStatementCallback());
    }
DRY不仅应用于编码

抽取出重复程序代码是运用DRY的好开始,但DRY的内涵可不只是如此!当试图避免重复程序代码时,实际也在试着确保你对应用程序中每一个功能和需求只实现一次。
其实无论编写需求,开发用例或者编写代码都应该遵守DRY原则!

举个我工作中的例子
关于红包回收业务需求
我们的业务需求文档写了如下需求:

  1. 红包过期应该进行自动回收
  2. 红包领取后30天内有效,过期应该回收。
  3. 红包活动过期,应该回收未使用的红包。

这个是明显的不遵循DRY,当然产品经理可能没有听说过DRY,如果你遇到了这种情况,请默默的在心里将需求凝练下即可。例如:

  1. 应按规则回收红包,规则如下:
    a. 未使用的在红包活动过期后回收
    b. 已领取部分使用的自领取之日起30天后进行回收
    c. 已使用完毕的不进行回收
SRP(单一职责)

系统中每一个对象应该具有单一职责,所有对象的服务都应该聚焦在实现该职责上。

应用举例

假设系统中有如下一个简单的Car类,其内部结果如下类图:

image

下面我们针对这个简单的例子,找出其不符合SRP的地方。

找出一个类中不符合SRP的方法为:

  1. 做填空,该 【XXX类】 自己 【XXX 方法】,找出语义不通顺的地方
  2. 结合自身业务理解进行进一步分析,最终确定不符合SRP的部分。

以Car类为例子 我们先进行第一步 :

该 Car 自己 start

该 Car 自己 stop

该 Car 自己 getOil

该 Car 自己 wash (?车自己洗车)

该 Car 自己 drive (?车自己驾驶,难道是自动驾驶的车)

我们找出两个方法可能不遵循SRP,一个是wash,一个是drive。

下面我们执行第二步,根据根据业务理解进行分析。
这里我们没有什么业务背景,仅依据生活经验进行分析。

  1. 车一般有其他人或机构进行清洗,不属于车的部分。应该从Car移除
  2. drive,处理自动驾驶车以外,车均由司机驾驶,自动驾驶车的驾驶员可以理解为电脑,所以drive也不属于Car类,应该从Car类移除。

从上边的小例子 我们可以看出:

  1. 方法名称要与具体实现的功能相符,否则第一步无法部分进行。

  2. 对业务的理解很重要,否则无法最终决定违反SRP的部分。
2点说明
  • DRY和SRP往往一同出现,DRY关注把一个功能片段放到一个单独的地方。
    SRP是关于一个类只做一件事。
  • 内聚力的另外一个名称就是SRP。
LSP(里氏替换原则)

子类型必须能够替换其基类型。

违反LSP的情形举例

假设我们有一个Graph2D 用于制作2D平面,现在要新创建一个Graph3D类,用于构建立体图,下面我们使用违反LSP原则的方式实现。

public static class Graph2D{
        int x;
        int y;

        public void setGraph(int x,int y){
            this.x=x;
            this.y=y;
        }
    }
    public static class Graph3D extends Graph2D{
        int z;

        public void setGraph(int x,int y,int z){
            this.x=x;
            this.y=y;
            this.z=z;
        }
    }
    public static void main(String[] args) {
        Graph3D Graph3D=new Graph3D();
        // 由于继承,使用者会非常迷茫,如何设置x,y,z
        Graph3D.setGraph(x, y);//来自父类Graph2D
        Graph3D.setGraph(x, y, z);//自己的
    }

上边的代码我们让Graph3D继承了Graph2D,造成Graph3D的使用者对setGraph产生了疑惑。 因为有2个setGraph方法。若不了解内部实现的人,将难以使用。

如何解决不满足LSP的情况

一共有3种处理方式:委托,聚合,组合。

委托

将特定工作的责任委派给另外一个类或方法。

如果你想要使用另一个类的功能性,但不想改变该功能,考虑以委托代替继承。

下面我们以委托的方式,解决上的问题,修改后代码,仅有一个setGraph方法,不会产生不必要的麻烦。
原本的类图为:

输入图片说明

以委托的方式修改后的类图,这时Graph3D依赖时Graph2D

image

相应的代码如下:

public static class Graph2D{
        int x;
        int y;

        public void setGraph(int x,int y){
            this.x=x;
            this.y=y;
        }
    }
    public static class Graph3D {
        int z;
        private Graph2D graph2D;//将平面部分委托给Graph2D处理
        public void setGraph(int x,int y,int z){
            graph2D.setGraph(x, y);
            this.z=z;
        }
    }
    public static void main(String[] args) {
        Graph3D graph3D=new Graph3D();
        graph3D.setGraph(x, y, z);
    }

组合

组合让你使用来自一组其他的行为,并且可以在运行时切换该行为。

组合类图举例:

image

在组合中,由其他行为组成的对象(本例子中是Unit类)拥有那些行为(本例中指Weapon的attack方法)。当拥有者对象被销毁时(Unit被销毁),其所有行为也被销毁(Weapon的所有实现也被销毁)。组合中的行为不存在组合之外。

聚合

当一个类被用作另一个类的一部分时,但仍然可以存在于该类之外。(组合单式没有结束)

聚合举例类图:

image

总结

类应该对扩展开发,对修改而关闭。(OCP)

通过将共同之物抽取出来并置于单一地方避免重复的程序代码(DRY)

系统中每一个对象应该具有单一职责,所有对象的服务都应该聚焦在实现该职责上。(SRP)

子类型必须能够替换其基类型。(LSP)

点击查看更多内容
4人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
JAVA开发工程师
手记
粉丝
17
获赞与收藏
111

关注作者,订阅最新文章

阅读免费教程

感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消