设计模式学习笔记(一)
设计模式学习笔记(一)
一般说设计模式都是指面向对象的设计模式,因为面向对象语言可以借助封装、继承、多态等特性更好的达到复用性、可拓展性、可维护性。
面向对象一般指以类、对象为组织代码的基本单元,并将封装、继承、多态、抽象四个特性(抽象有的定义里并不认为是四大特性)作为代码设计与实现的基石。
-
封装:通过访问权限控制,只对外暴露必要的操作,保护数据。
-
继承:代码复用,结构美感。不过 Java 语言不支持多重继承,原因是如果 BC 都继承了 A 并重写了某个方法,D 同时继承 BC 会产生歧义。
-
多态:提高代码的复用性,主要通过两种方式实现
- 继承:父类引用指向子类对象
- 实现:接口引用指向具体实现类
-
抽象:有时并不计入四大特性,用来保护实现,例如接口就是对实现的一种抽象,无需关注实现
有些设计看似是面向对象
- 滥用 getter、setter 方法。lombok 的注解确实很方便,但这样其实违背了面向对象的封装特性,例如 createTime 等字段其实是不需要 setter 方法的,需要在创建对象的时候就确定。
- 滥用全局变量、全局方法(Constants、Utils)。这样会导致修改后所有引用的地方都重新编译,而且有的时候只需要其中的某几个变量(或方法)却导入了整个类。
- 功能拆分,不要定义一个大而全的类。例如 Constants 拆分为 DateConstants、RedisConstants、MysqlConstants
定义数据与方法分离的类
。传统的 MVC 开发中,数据在相应的 BO、VO、PO 中,而操作却封装在对应的 Controller、Service 中,这就是典型的面向过程,也就是“贫血模型”
的开发方法。不过这样的开发方式依然很流行,因为大部分的需求并不复杂,甚至只是从数据库中找到查哪些字段,组织对应的 VO,先写 service 反推 controller。
如何理解接口与抽象类
随着 jdk 版本的更新,接口也可以有默认实现,也可以定义变量作为常量使用。抽象类依然不允许被实例化,继承抽象类必须重写抽象类的所有方法。
先说结论:抽象类的作用更多是为了代码复用,而接口的作用则更偏向与“协议”,具备什么样的功能。
public class BaseEntity implements Serializable {private static final long serialVersionUID = 8417380540303280008L;@ApiModelProperty(value = "所属用户标识")@Column(name = "USER_ID")private String userId;@ApiModelProperty(value = "记录是否有效,默认为1表示有效")@Column(name = "ACTIVE")private String active;@ApiModelProperty(value = "创建时间,默认为当前时间")@Column(name = "CREATED_AT", updatable = false)@DateTimeFormat(pattern = Constants.PATTERN_DATE_HOUR_MINUTE_SECOND)@JsonFormat(pattern = Constants.PATTERN_DATE_HOUR_MINUTE_SECOND, timezone = Constants.TIMEZONE)private Date createdAt;@ApiModelProperty(value = "更新时间")@Column(name = "UPDATED_AT")@DateTimeFormat(pattern = Constants.PATTERN_DATE_HOUR_MINUTE_SECOND)@JsonFormat(pattern = Constants.PATTERN_DATE_HOUR_MINUTE_SECOND, timezone = Constants.TIMEZONE)private Date updatedAt;}
例如我们有一个上面的类,对于一个正常的删除来讲,一方面我们要查询这个数据是否存在(例如有些系统删除不存在的空数据会返回错误),另一方面判断当前登录用户是否具有删除权限(即资源的 USER_ID 是否为当前登录人或是否是当前登录人的下属),最后还需要记录日志。
public void delete(String uuid) {Entity entity = getEntityFromDB(uuid);if (entity == null) {throw ......}if (entity instanceof BaseEntity) {BaseEntity e = (BaseEntity)entity;if (!e.getActive.equals("1")) {} else {e.setActive("0");saveEntityToDB(entity);}}
}protected abstract T saveToDataBase(T entity);protected abstract Entity getEntityFromDB(String uuid);
借助抽象类与多态,可以提高代码的复用性,减少重复代码。
如何理解基于接口而非实现编程
假如目前有一个上传图片到公有云的需求
public class uploadPictureAliyunImpl {// 获取合法 tokenpublic String getToken() {};// 如果目录不存在就创建目录public boolean createDictoryIfNotExists() {};// 上传图片public boolean uploadPictureToAliyun() {};}public class Main() {public static void Main () {uploadPictureAliyunImpl impl = new uploadPictureAliyunImpl();String token = impl.getToken();........}
}
如果这样实现,后期替换为其他云厂商,例如自有的私有云,就需要替换很多代码,实际上这种情况只需要定义一个上传图片的接口,由不同的存储来实现就行。
基于接口编程,即进行更好的抽象设计,不暴露过多实现。
为什么说多用组合少用继承
以鸟(bird)为例,可以分为是否会飞、是否会下蛋…
当继承层次越来越深,关系会越来越复杂,会严重影响代码的稳定性与可维护性。但是当继承层次很浅且业务稳定时,依然可以利用继承和多态特性来实现特定功能。
继承实际上可以替换为组合来实现,例如定义两个接口:
public interface Flyable {void fly();
}public interface Eggable {void egg();
}
每一种鸟类根据自己的情况来实现对应接口即可,但是这会引入新的问题,例如有 n 个鸟类实现的 fly 接口都是一样的,那代码重复会十分严重,解决方式就是“委托”:
public class DefaultFlyableImpl implements Flyable {void fly() {......}
}public class AAABird implements Flyable {// 其实一般是使用注解注入private DefaultFlyableImpl defaultFlyImpl = new DefaultFlyableImpl();void fly() {defaultFlyImpl.fly();}}