• zeta
    2019-11-22
    其实这篇和上一篇可以讲的更好的。首先,我反对接口是has-a的说法,我坚持接口的语义是behaves like(这个其实我也是在某一本书上看的). 咱们看下哪个更通顺和达意,A AliyunImageStorage has a DataStorage. or A AliyunImageStorage behaves like a DataStorage? 除非你在第一句加上 A AliyunImageStorage has some behaviors of DataStorage. 但这基本也就是behaves like的意思了。
    第二,我觉得咬文嚼字的确没有什么意义,但为什么说上述话题,难道讲接口的例子不用出现接口多重继承么,引用我之前留言:拿一个C++中举的多重继承例子来说,吸血鬼分别继承自蝙蝠和人,那么吸血鬼is a蝙蝠么?吸血鬼is a人么?所以其实两个都不是,这就是设计上的语义问题。这里缺失了除了is a的另一个概念,behaves like,也就是多重继承的真义实际上是behaves like,也就是接口的意义。A vampire behaves like humans and bats. 而这是接口能多重的原因,一个类可以具有多重行为,但是不能是多种东西。
    所以其实也就是说,只有当前模块涉及到抽象行为的时候,才有必要设计接口,才有可能利用接口多重继承的特性来更好的将各种行为分组。
    展开
     28
     163
  • helloworld
    2019-11-23
    到目前为止老师所讲理的的理论都懂~至于思考题用简单工厂,反射等方式感觉都不行。给老师提个小小的建议:能不能和隔壁的『MySQL实现45讲』的专栏一样在下一节课程的末尾集中回答一下上一节课程的课后习题?感谢
     3
     83
  • 香蕉派2号
    2019-11-22
    思考题
    解决方案=配置文件+反射+工厂模式
     12
     58
  • 业余爱好者
    2019-11-22
    关于抽象和函数命名的问题,不知道哪个大佬说过这么一句话:

       每个优秀的程序员都知道,不应该定义一个attackBaghdad() ‘袭击巴格达‘ 的方法,而是应该把城市作为函数的参数 attack(city)。
    
     52
  • 辣么大
    2019-11-22
    关于思考题我想出两种方法改进:简单工厂方法和使用反射。

    1、简单工厂方法
    ImageStore imageStore = ImageStoreFactory.newInstance(SOTRE_TYPE_CONFIG);
    config文件可以写类似properties的文件,使用key-value存储。

    缺点:再新增另一种存储手段时,需要修改工厂类和添加新的类。修改工厂类,违反了开放-封闭原则。

    那有没有更好一点的方法呢?

    2、使用反射。
    在配置文件中定义需要的image store类型。
    在ProcessJob中
    ImageStore store = (ImageStore) Class.forName(STORE_CLASS)
        .newInstance();

    缺点:使用反射,在大量创建对象时会有性能损失。

    关于减少ProcessJob中的修改,还有没有更好的方法呢?我只是抛砖引玉,希望和大家一起讨论。具体实现:https://github.com/gdhucoder/Algorithms4/tree/master/geekbang/designpattern/u009

    补充:
    关于access token:Aliyun的AccessToken时有expireTime时限的。不需要每次重新获取,过期时重新获取即可。
    展开
     12
     34
  • 秋惊蛰
    2019-11-22
    依赖注入,从外部构建具体类的对象,传入使用的地方
     1
     26
  • 编程界的小学生
    2019-11-22
    首先这篇文章受益匪浅,尤其是第二点,与特定实现有关的方法不要暴露到接口中,深有体会。

    其次问题解答
    我个人的解决方案是这种情况不要去直接new,而是用工厂类去管理这个对象,然后名字可以起成getInstance这类不包含某个具体实现的含义的抽象名称。将来修改直接修改工厂类的getInstance方法即可,这种方式可取吗?还有其他更好的方式吗?求老师点评。
    
     16
  • 守拙
    2019-11-22
    课堂讨论answer:
    考虑使用工厂模式生成ImageStore实例.这样就可以将调用者和具体ImageStore解耦.

    例:
    public class ImgStoreFactory {

        private ImgStoreFactory(){

        }

        public static ImageStore create(Class<?> clz){
            if (clz == AliyunStore.class){
                return new AliyunStore();
            }else if (clz == PrivateYunStore.class){
                return new PrivateYunStore();
            }else {
                throw new IllegalStateException("..");
            }
        }

        public static void main(String[] args) {
            ImageStore store = ImgStoreFactory.create(AliyunStore.class);
            store.dosth();
        }
    }

    另外有一点不太同意作者的说法:
    上节课作者将Contract翻译为"协议",
    我认为是不恰当的.
    在计算机领域, 通常使用Protocol代表协议.
    个人认为Contract更恰当的翻译是"契约".
    展开
     3
     14
  • 失火的夏天
    2019-11-22
    思考题估计就是要引出工厂模式了吧
    
     14
  • 雷霹雳的爸爸
    2019-11-22
    要不是有一开始的课程大纲,我以为课堂讨论是要启发大家,在下节就要讲创建型模式,工厂模式,工厂方法什么的了

    但转念一想,这想法或许太肤浅了,毕竟大多数创建型方法都有一个明显的对具体类型的依赖(这里先预先排除抽象工厂,觉得有点小题大做这样搞),都不是一个最终能让人感到内心宁静的做法

    这节既然讲的是依赖于抽象而不是依赖于具体,那比较得瑟的玩儿法恐怕应该是直接在ImageProcessingJob类和ImageStore接口这两个类型关系上充分体现出依赖倒置的思路,把最后一点执行创建ImageStore类型实例的痕迹彻底关在ImageProcessingJob的门外,虽然必然得有人去考虑实际至少调一下ImageStore具体类型实例的这个创建过程,但ImageProcessingJob这爷是不打算操心这事了,它只需要留个口子,让别人把ImageProcessingJob放到自己锅里,自己就可以开始炒菜了

    也就是从形式上,ImageProcessingJob这个类只需要保留对ImageStore接口的依赖就可以了,具体留口子的手段则要考虑依赖注入,形式上有两种:

    + 一种是可能更OO样子的一点,即声明一个ImageStore的field在ImageProcessingJob类里面
      - 如果说有什么好处,恩,可以理解为能对客户程序隐藏了ImageStore类型的信息,是的,连类型信息都隐藏掉;好吧,还是得关心别人,毕竟这世界上不是仅有自己一个
      - 具体操作起来,由于不能声明field时候直接new,要不又变回去了,但又不能NPE吧,所以不考虑创建,我还是得考虑怎么把实例请进来,就是上面说的至少留个口子
      - 这时候可能不得不借助依赖注入的帮助了(否则就是依赖查找,还是工厂),即
      - 通过ImageProcessingJob的构造函数注入或者利用field注入来获取ImageStore接口的实例,或者ImageProcessingJob如果依赖项多,Builder一下也很好
      - 毕竟ImageProcessingJob这个类型在我们讨论的上下文里面是如此具体的一个类,就不过分追溯它的创建责任及执行在哪里了
    + 另一种,表面粗暴直接看似问题多多,但是细品也有点意思的,那就是process方法直接增加一个ImageStore的参数就完了,OMG我在干什么
      - 没有B方案的设计自身无法证明自己更好
      - 相对于上面的,直接的问题是会对process方法直接依赖的客户程序会和ImageStore这个类型产生耦合
        * 如果客户程序是一个类(还能是什么?),要么有一个field等着inject进来,要么是通过调用process的method传进来,要么就是无中生有(直接new了或用创建型模式)
        * 这都可能造成没有充分的设计隔离,至少让客户程序造成信息冗余,承担了不必要的职责等问题
      - 但事实上也不是完全没好处,这种灵活性体现在它没有把ImageStore的逻辑固化在任一个ImageProcessingJob实例里面
        * 考虑上面第三类无中生有的方式,假设是创建型的工厂方法或类似手段,则可以提供对method参数(业务层面的动态输入,例如最终操作用户的提供的值)的响应能力
        * 这和,执行排序算法骨架确定,但是需要给定两个元素(复杂对象)比较规则这种思路有相似之处,毕竟我需要的是对方的能力而不是对方的数据或者数据视图,这时候这么做还是很有诱惑力的
      - 如果脱离开场景,实际上这种动态性还更强,但问题就在于这种动态性会不会对于具体场景有价值
      - 从这个实例上看,也许没这么明显,因为不同的对象存储后端更有可能是环境(测试、生产?但12 factor让我们...好歹测试环境还是也上云吧)不同造成的,而非基于动态的用户信息输入
      - 但,事无绝对吧,假设,用户有选择我要针对具体这一张,我特么上传那一刻选择一个存储后端的需求
        - 然后为了方便用户,用户竟然可以勾选,以后使用同样地选择...
        - 我觉得除了脑子进水的犬类应该没人会干这种没问题制造问题也要上的方案吧
     
     所以综上所属,还是field一个ImageStore接口来搞吧

    这极客时间也让人想吐槽,能敲2000字,结果就只留这么大点儿一个输入框...你要不就限制200字我还能少敲一点...我这写的兴起还得外面写完了贴过来...
    展开
     2
     9
  • William
    2019-11-22
    所以思考题,想到的是,将接口作为构造函数中的参数,传递进来,再调用.
     4
     7
  • Monday
    2019-11-22
    依赖注入可以解决思考题,基于接口的实现有多种时,注入处也需要指明是哪咤实现
    
     6
  • YouCompleteMe
    2019-11-22
    抽象工厂,把创建具体类型放到工厂类里
    
     6
  • 程斌
    2019-11-22
    存储图片的方式写入到配置文件,第8行改用传入类型参数来实例化不同的对象,明天补上代码。
    
     6
  • 二星球
    2019-11-23
    使用策略模式,在建一个Context类,使用聚合持有这个接口实例引用,其它所有地方都用这个context类,变动的时候,只变这个context类就行了,其它不动
    
     5
  • Milittle
    2019-11-22
    再说一句 面向接口编程的精髓 我的理解是我们在使用接口的时候 关心我们要做什么 而不是怎么做 怎么做都封装在具体实现类中。而且最主要的是 接口抽象
    
     5
  • NoAsk
    2019-11-22
    关于什么时候定义接口的一些拙见:
    当方法会有其他实现,或者不稳定的时候需要定义接口;
    1.不稳定的方法一般能事先确定,用接口能提高可维护性
    2.但在开发时往往不确定是否需要其他实现,我的原则是等到需要使用接口的时候再去实现。所以根据kiss原则一般我会先用方法实现,如果有一天真的需要有新的实现的时候再重新抽象出接口对代码进行小重构。

    就老师的例子进行一下说明:
    刚开始只需要阿里云进行图片上传下载功能,我就先只实现阿里云的图片上传下载方法。
    后期发现需要有私有云的上传下载方法的话,那就对这个功能通过接口进行抽象。但是你永远不知道到底是新的图片上传下载功能先来到还是其他阿里接口先来到,如果是新的阿里接口,也是用的这一套token方法,那就用抽象方法或接口对token部分实现抽象。

    课后问题:
    简单工厂,工厂模式可以提高可扩展性,维护性。
    java spring项目可以使用注入的方式。
    展开
    
     4
  • bearlu
    2019-11-22
    老师,希望能把示例代码和问题代码也放到Github上。

    作者回复: 👌 我抽空整理一下放上去
    https://github.com/wangzheng0822

     1
     3
  • 超威丶
    2019-11-22
    个人觉得维护map是最好的选择,实现类型和具体实现对应。
     1
     3
  • Paul Shan
    2019-11-22
    基于抽象而非具体体现了信息隐藏和分离代码中稳定性不同的部分。在一个上传图片的部分不需要知道图片是如何上传的,阿里云以及token就属于过多的信息,有必要隐藏这些信息。另外一方面上传图片这件事比阿里云实现要稳定的多,不上传图片的概率低于不用阿里云上传图片的概率。这里有必要分离图片上传这个接口和用阿里云上传这个实现。
    不过原来的实现也没什么问题,毕竟谁也不能未卜先知,将来一定会替换阿里云。如果我拿到这个变更需求,我会先用同名接口替换原来的实现(原来的阿里类实现清晰,功能单一,只是不适合直接调用),然后用adapter来转接口,然后一步一步实现接口和实现的分离,目标是接口能够隐藏信息,实现能够清晰明了,每一步都能用IDE工具重构,每一步都能编译和测试。
    
     3
我们在线,来聊聊吧