前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >面向对象设计4原则 原

面向对象设计4原则 原

作者头像
温安适
发布2019-03-12 16:44:29
5020
发布2019-03-12 16:44:29
举报

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
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
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
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
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
image

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

聚合

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

聚合举例类图:

image
image

总结

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

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

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

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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2017/05/02 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • OCP(开闭原则)
    • 应用举例
      • OCP不仅仅是继承
      • DRY(不自我重复)
        • 举例说明
          • DRY不仅应用于编码
          • SRP(单一职责)
            • 应用举例
              • 2点说明
              • LSP(里氏替换原则)
                • 违反LSP的情形举例
                  • 如何解决不满足LSP的情况
                    • 委托
                    • 组合
                    • 聚合
                • 总结
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档