抽象类:(is-a)
接口类:(has-a)
抽象类是多个子类代码重复,对整体重复代码抽离出来,是自下而上的。
接口偏重于解耦,是对行为的抽象,相对于一组协议或者契约,构建接口经常是自上而下的。
public class Logger {
private String name;
private boolean enabled;
private Level minPermittedLevel;
public Logger(String name, boolean enabled, Level minPermittedLevel) {
this.name = name;
this.enabled = enabled;
this.minPermittedLevel = minPermittedLevel;
}
protected boolean isLoggable(Level level) {
boolean loggable = enabled & (minPermittedLevel.intValue <= level.intValue);
return loggable;
}
}
// 文件子类
class FileLogger extends Logger {
private Writer writer;
private String filePath;
public FileLogger(String name, boolean enabled, Level minPermittedLevel, String filePath) {
super(name, enabled, minPermittedLevel);
this.filePath = filePath;
}
public void log(Level level, String message) {
if (!isLoggable(level)); return;
writer.write(message);
}
}
// 中间间输出子类
class MessageQueueLogger extends Logger {
private MessageQuesueClient messageQuesueClient;
public MessageQueueLogger(String name, boolean enabled, Level minPermittedLevel, MessageQuesueClient messageQuesueClient) {
super(name, enabled, minPermittedLevel);
this.messageQuesueClient = messageQuesueClient;
}
public void log(Level level, String message) {
if (!isLoggable(level)); return;
messageQuesueClient.send(message);
}
}
固然说这样子达到了代码复用的目的,但是已经无法使用多态特性了,不能用logger去调用log方法
还有一种,就是写一个空log()
,但是相对于抽象类而言,还是脱了裤子放屁,而且定义一个空方法,其他人也不容易去理解,也有可能忘记了该方法的实现
接口就是没有定义成员变量,只有方法声明,没有方法实现
class Strategy {
public:
~Strategy();
virtual void algorithm()=0;
protected:
Strategy();
}
没有定义任何属性,且所有方法都声明为virtual类型,这样所有方法都没有代码实现,且继承这个抽象类的子类都必须实现这些方法,从语法特性来说,相当于一个接口。
public calss MockInterface {
protected MockInterface() {}
public void funcA() {
throws new MethodUnSupportedException();
}
}
该类构造方法是protected,只可以继承。且不重写funA,会抛出MethodUnSupportedException异常,这样就强迫子类在继承这个夫类时,都要主动实现夫类的方法。
抽象类的概念是有属性,有方法,可以有方法没有实现
用普通类实现抽象类
public calss MockInterface {
private int value;
protected MockInterface() {}
public void funcA() {
throws new MethodUnSupportedException();
}
}
和模拟接口类似,将构造方法保护起来,只能通过继承来实现,且未实现方法需要定义抛出异常
广泛的对于接口的定义: 一组协议或者约定,是功能提供者提供给使用者的一个功能列表
越抽象、越顶层、越脱离具体某一实现的设计,越能提高代码的灵活性,越能应对未来的需求变化。好的代码设计,不仅能应对当下的需求,而且在将来需求发生变化的时候,仍然能够在不破坏原有代码设计的情况下灵活应对。
以图片上传为例
现在有一个向阿里云上传图片的类,如下
public class AliyunImageStore {
// ... 省略属性、构造方法....
public void createBucketIfNotExisting(String bucketName) {
// 创建bucket代码逻辑。。。
// 。。。失败抛出异常
}
public String generateAccessToken() {
// 根据 accesskey / secretkey 等生成access token
}
public String uploadToAliyun(Image image, String bucketName, String accessToken) {
// 上传图片到阿里云
// 返回图片储存地址
}
public Image downloadFromAliyun(String url, String accessToken) {
// 从阿里云下载图片返回
}
}
// AliyunImageStore类使用实例
public class ImageProcessingJob {
private static final String BUCKET_NAME = "al_image_bucket";
// .....
public void process() {
Image image = ...; // 处理图片,并封装为Image对象
AliyunImageStore imageStore = new AliyunImageStore();
imageStore.createBucketIfNotExisting(BUCKET_NAME);
String accessToken = imageStore.generateAccessToken();
imageStore.uploadToAliyun(image, BUCKET_NAME, accessToken);
}
}
上述流程分为了三个步骤:创建bucket(存储目录),生成accessToken,携带token上传图片到指定的buckey中。整个流程看起来没有什么问题。
现在不往阿里云上放东西了,改成私有云了,那这个类就需要进行大改,如生成token,类名,方法名等。而且我们对这个行为进行抽象,他们都具有上传和下载功能,姑可以将该方法抽象出来。
public interface ImageStore {
String uploadImage(Image image, String bucketName);
Image download(String url);
}
// 阿里云存储
public class AliyunImageStore implements ImageStore{
// ... 省略属性、构造方法....
public void createBucketIfNotExisting(String bucketName) {
// 创建bucket代码逻辑。。。
// 。。。失败抛出异常
}
public String generateAccessToken() {
// 根据 accesskey / secretkey 等生成access token
}
@Override
public String uploadImage(Image image, String bucketName) {
createBucketIfNotExisting(bucketName);
String token = generateAccessToken();
// 上传图片到阿里云
// 返回图片储存地址
}
@Override
public Image download(String url) {
String token = generateAccessToken();
// 从阿里云下载图片返回
}
}
// 本地云存储
public class PrivateImageStore implements ImageStore{
public void createBucketIfNotExisting(String bucketName) {
// 创建bucket代码逻辑。。。
// 。。。失败抛出异常
}
@Override
public String uploadImage(Image image, String bucketName) {
createBucketIfNotExisting(bucketName);
// 上传图片到私有云
// 返回地址
}
@Override
public Image download(String url) {
// 从私有云下载图片。。。
}
}
// 调用
public class ImageProcessingJob {
private static final String BUCKET_NAME = "al_image_bucket";
// .....
public void process() {
Image image = ...; // 处理图片,并封装为Image对象
ImageStore imageStore = new AliyunImageStore();
imageStore.uploadImage(image, BUCKET_NAME);
}
}
注意:
上述的使用也是存在问题的
ImageStore imageStore = new AliyunImageStore(); //更换图片存储方法不方便
解决思路:
继承解决了代码复用问题,但是如果继承层次过深,过复杂,也会影响到代码的可维护性。
例如要设计一个鸟类
这样的一个抽象的事物概念,定义为一个抽象类AbstractBird。
现在有需求:鸟会飞,会跑,会下蛋,我们是否要定义这些方法
那不会飞,不会跑,不会下蛋的鸟也很多,那该怎么办呢?
就拿飞举例,给不会飞的鸟抛出异常,这固然是一种方法,还有就是再通过AbstractBird派生出两个更加细分的类,会飞的鸟和不会飞的鸟,哪再加上会跑,2*2就是要定义4个了,再有具体的要求,结构就越来越复杂,而且更改父类逻辑,子类也会收到影响。
常用的三种方式:组合,接口,委托,一块解决继承的问题。
对于会飞的特性,定义一个flyable(),去实现这个接口,将实现类放入鸟类的成员变量中去,如果要调用fly的方法,就可以通flyable的实现类进行调用
尽管我们鼓励多用组合少用继承,但组合也并不是完美的,继承也并非一无是处。在实际的项目开发中,我们还是要根据具体的情况,来选择该用继承还是组合。如果类之间的继承结构稳定,层次比较浅,关系不复杂,我们就可以大胆地使用继承。反之,我们就尽量使用组合来替代继承。除此之外,还有一些设计模式、特殊的应用场景,会固定使用继承或者组合。