美文网首页
2021-12-11 面向对象--抽象、接口总结

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

作者: 竹blue | 来源:发表于2021-12-18 13:58 被阅读0次

    抽象类

    语法特性

    • 抽象类不允许被实例化,只能被继承,直接new 抽象类编译会报错。
    • 抽象类可以包含属性和方法,方法可以包含代码实现也可以不包含代码实现(抽象方法)。
    • 子类继承抽象类,必须重写全部的抽象方法。

    场景

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

    意义

    • 抽象类可以解决代码复用问题
    • 抽象类相对于继承来说实现思路更优雅。-- 如果仅仅用继承的化,会出现如下三种风险:
      1. 影响代码可读性,如果下代码:Logger中定义一个空方法,会影响代码的可读性,需要Logger、FileLogger、MessageQueueLogger 之间的继承关系,才能弄明白其设计意图。
      2. 如果方法较多的情况下,容易忘记重写。
      3. 增加了方法误用的风险,如Logger方法实例化之后调用自身的空方法log();
    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 关系来替代多态特性我们可以利用接口来实现代码复用我们可以通过组合和委托来实现。所以,从理论上讲,通过组合、接口、委托三个技术手段,我们完全可以替换掉继承,在项目中不用或者少用继承关系,特别是一些复杂的继承关系。

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

    • 如果类之间的继承结构稳定(不会轻易改变),继承层次比较浅(比如,最多有两层继承关系),继承关系不复杂,我们就可以大胆地使用继承。反之,系统越不稳定,继承层次很深,继承关系复杂,我们就尽量使用组合

    • 设计模式会固定使用继承或者组合。比如,装饰者模式(decorator pattern)、策略模式(strategy pattern)、组合模式(composite pattern)等都使用了组合关系,而模板模式(template pattern)使用了继承关系。

    • 特殊的应用场景,会固定使用继承或者组合。比如Crawler 类和 PageAnalyzer 类,它们都用到了 URL 拼接和分割的功能,但并不具有继承关系(既不是父子关系,也不是兄弟关系)这个时候,使用组合就更加合理、更加灵活;同理:如果你不能改变一个函数的入参类型,而入参又非接口,为了支持多态,只能采用继承来实现

      /**
       * 需求:请设计出:一个关于鸟的特征关系类图。
       *      要求如下:
       *              会叫:鸵鸟、麻雀
       *              会飞:麻雀
       *              会下蛋:鸵鸟、麻雀
       * 思路:1. 针对“会飞”这样一个行为特性,我们可以定义一个 Flyable 接口,只让会飞的鸟去实现这个接口。
       *         对于会叫、会下蛋这些行为特性,我们可以类似地定义 Tweetable 接口、EggLayable 接口
       *      2. 针对三个接口再定义三个实现类,它们分别是:实现了 fly() 方法的 FlyAbility 类、
       *         实现了 tweet() 方法的 TweetAbility 类、实现了 layEgg() 方法的 EggLayAbility 类。
       *         然后,通过组合和委托技术来消除代码重复
       */
      public interface Flyable {
          /**
           * 飞
           */
          void fly();
      }
      
      public interface Tweetable {
          /**
           * 叫
           */
          void tweet();
      }
      
      public interface EggLayable {
          /**
           * 下蛋
           */
          void layEgg();
      }
      
      public class FlyAbility implements Flyable{
      
          public void fly() {
              // ...
          }
      }
      
      public class TweetAbility implements Tweetable{
          public void tweet() {
              // ...
          }
      }
      
      public class EggLayAbility implements EggLayable{
      
          public void layEgg() {
              // ...
          }
      }
      
      public class Ostrich implements Tweetable, EggLayable {//鸵鸟
      
          private Tweetable tweetable = new TweetAbility();//组合
      
          private EggLayAbility eggLayAbility = new EggLayAbility(); //组合
      
          public void layEgg() {
              eggLayAbility.layEgg();//委托
          }
      
          public void tweet() {
              tweetable.tweet();//委托
          }
      }
      
      public class Sparrow implements Flyable, Tweetable, EggLayable {//麻雀
      
      
      
          private Tweetable tweetable = new TweetAbility();//组合
      
          private EggLayAbility eggLayAbility = new EggLayAbility();//组合
          
          private Flyable flyable = new FlyAbility();//组合
      
          public void layEgg() {
              eggLayAbility.layEgg();//委托
          }
      
          public void tweet() {
              tweetable.tweet();//委托
          }
      
          public void fly() {
              flyable.fly();//委托
          }
      }
      

    相关文章

      网友评论

          本文标题:2021-12-11 面向对象--抽象、接口总结

          本文链接:https://www.haomeiwen.com/subject/hmlgfrtx.html