2021-12-11 面向对象--抽象、接口总结

2021-12-18  本文已影响0人  竹blue

抽象类

语法特性

场景

如果是is-a的关系,并且为了解决代码复用的问题。

意义

public class Logger {

    private String name;
    private boolean enabled;
    private Level minPermittedLevel;

    protected boolean isLoggable(Level level) {
        return enabled && (minPermittedLevel.intValue() <= level.intValue());
    }
    //
    public void log(Level level, String mesage) { // do nothing... }

    public static void main(String[] args) {
        //case1: 父类:非抽象类,就是普通的类. 删除了log(),doLog(),新增了isLoggable()               
        //Logger 中并没有定义 log() 方法 ,编译报错
        //Logger logger = new FileLogger("access-log", true, Level.WARN, "*.log");
        //logger.log(Level.ERROR, "This is a test log message.");


        //case2: Logger中定义log()空方法,实际调用了Logger中的空方法。
        Logger logger = new Logger("access-log", true, Level.WARN, "*.log");
        logger.log(Level.ERROR, "This is a test log message.");
    }
}

/**
 * 输出日志到文件
 */
public class FileLogger extends Logger {

    private Writer fileWriter;

    public void log(Level level, String mesage) throws IOException {
        if (isLoggable(level)) {
            return;
        }
        fileWriter.write("");
    }
}

/**
 * 输出日志到消息中间件(比如kafka)
 */
public class MessageQueueLogger extends Logger {
    private MessageQueueClient msgQueueClient;

    public void log(Level level, String mesage) {
        if (!isLoggable(level)) return;
        // 格式化level和message,输出到消息中间件
        msgQueueClient.send("");
    }
}

接口

语法特性

场景

如果是has-a 的关系,并且为了提供代码扩展性问题。

意义

接口是对方法的抽象,是一种 has-a 关系,表示具有某一组行为特性,是为了解决解耦问题,隔离接口和具体的实现,提高代码的扩展性

抽象类和接口的区别

什么时候该用抽象类?什么时候该用接口?实际上,判断的标准很简单。如果要表示一种 is-a 的关系,并且是为了解决代码复用问题,我们就用抽象类;如果要表示一种 has-a 关系,并且是为了解决抽象而非代码复用问题,那我们就用接口。

基于接口而非实现编程原则

原则中”接口“的理解

设计初衷:“接口”就是一组“协议”或者“约定”,是功能提供者提供给使用者的一个“功能列表”。该原则可以有效的提供代码质量。应用这条原则,可以将接口和实现相分离,封装不稳定的实现,暴露稳定的接口。上游系统面向接口而非实现编程,不依赖不稳定的实现细节,这样当实现发生变化的时候,上游系统的代码基本上不需要做改动,以此来降低耦合性,提高扩展性。

在软件开发中,最大的挑战之一就是需求的不断变化,这也是考验代码设计好坏的一个标准。越抽象、越顶层、越脱离具体某一实现的设计,越能提高代码的灵活性,越能应对未来的需求变化。好的代码设计,不仅能应对当下的需求,而且在将来需求发生变化的时候,仍然能够在不破坏原有代码设计的情况下灵活应对

注意事项

  1. 函数的命名不能暴露任何实现细节,命名要足够通用,不能包含跟具体实现相关的字眼
  2. 封装具体的实现细节,与特定实现有关的方法不要定义在接口中

使用场景

从设计初衷上来看,如果在我们的业务场景中,某个功能只有一种实现方式,未来也不可能被其他实现方式替换,那我们就没有必要为其设计接口,直接使用实现类就可以了。

越是不稳定的系统,我们越是要在代码的扩展性、维护性上下功夫。相反,如果某个系统特别稳定,在开发完之后,基本上不需要做维护,那我们就没有必要为其扩展性,投入不必要的开发时间。

/**
 * 需求:存储图片接口用于上传、下载。
 *
 */
public interface ImageStore {
    /**
        *接口命名没有暴露细节,且足够通用。
        */
    public String upload(Image image, String bucketName);

    Image download(String url);
}

public class AliyunImageStore implements ImageStore {

    public String upload(Image image, String bucketName) {
        createBucketIfNotExisting(bucketName);

        String accessToken = generateAccessToken();
        //...上传图片到阿里云...
        // ...返回图片在阿里云上的地址(url)...
        return "aliyunUrl";
    }

    public Image download(String url) {
        String accessToken = generateAccessToken();
        //...从阿里云下载图片...
    }

    /**
     * 封装具体的实现细节。跟阿里云相关的流程不应该暴露给调用者。
     *
     */
    private void createBucketIfNotExisting(String bucketName) {
        // ...创建bucket...
        // ...失败会抛出异常..
    }

    private String generateAccessToken() {
        // ...根据accesskey/secrectkey等生成access token
    }
}

组合和继承

继承存在的问题

继承是面向对象的四大特性之一,表示is-a的关系,用于解决代码复用的问题,但是如果继承层次过深,关系过复杂,也会影响到代码的可读性和可维护性

组合相对于继承的优势?

继承主要有三个作用:表示 is-a 关系,支持多态特性,代码复用。而这三个作用都可以通过其他技术手段来达成。比如 is-a 关系,我们可以通过组合和接口的 has-a 关系来替代多态特性我们可以利用接口来实现代码复用我们可以通过组合和委托来实现。所以,从理论上讲,通过组合、接口、委托三个技术手段,我们完全可以替换掉继承,在项目中不用或者少用继承关系,特别是一些复杂的继承关系。

组合和继承分别的使用场景?

上一篇 下一篇

猜你喜欢

热点阅读