• 【Dart】dart之mixin探究


    由于dart中是没有interface的,在dart中我们需要定义接口的话用的是class关键字

    implements是把某个class当做接口来实现要求我们重写这个class的所有方法

    注意:implements会将class的实现抹掉就不存在默认实现一说,而dart也是不允许多继承的。

    extends表示继承某个class,可以继承父类实现了的方法

    mixin实际上也是面向对象编程中的概念,用户与Mixin不是“is-a”的关系,而是“-able”关系

    Mixin是面向对象程序设计语言中的类,提供了方法的实现。其他类可以访问mixin类的方法而不必成为其子类。[1]Mixin有时被称作"included"而不是"inherited"。mixin为使用它的class提供额外的功能,但自身却不单独使用(不能单独生成实例对象,属于抽象类)。因为有以上限制,Mixin类通常作为功能模块使用,在需要该功能时“混入”,而且不会使类的关系变得复杂。

    dart语言里面我们可以使用with关键字实现mixin,将一个或者多个class混入另一个类:

    class Base1 {
      void foo1() {
        print("foo1");
      }
    }
    
    class Base2 {
      void foo2() {
        print("foo2");
      }
    }
    
    class Child2 with Base1, Base2 {
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    没错,通过with多个类,可以实现类似多继承的效果。

    既然允许with多个类,那么如果这些类中有个相同方法,那会出现什么事情?

    实际上kotlin、java8使用接口的默认实现也会出现一样的问题,他们的处理方法是当出现相同方法的时候实现类需要手动指定使用哪个接口的默认实现,要不然编译会报错:

    // java8
    interface IBase1 {
        default void foo() {
            System.out.println("1");
        }
    }
    
    interface IBase2 {
        default void foo() {
            System.out.println("2");
        }
    }
    
    class Child implements IBase1, IBase2 {
    
        @Override
        public void foo() {
            IBase2.super.foo();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    //kotlin
    interface IBase1 {
        fun foo() {
            println("1")
        }
    }
    
    interface IBase2 {
        fun foo() {
            println("2")
        }
    }
    
    class Child : IBase1, IBase2 {
        override fun foo() {
            super<IBase2>.foo()
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    必须提到的是:无论是 extends、implements 还是 mixin,优先级最高的是在具体类中的方法。

    线性化

    而在dart中with里面越后面的类优先级越高:

    class Base1 {
      void foo() {
        print("1");
      }
    }
    
    class Base2 {
      void foo() {
        print("2");
      }
    }
    
    class Base3 {
      void foo() {
        print("3");
      }
    }
    
    class Child extends Base1 with Base2, Base3 {}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    这个时候调用Child.foo方法实际会优先调用Base3.foo。原因是dart实际是通过创建中间类继承实现的mixin,上面的代码相当于:

    img

    通过从左到右的顺序生成中间父类去继承将extends、with线性化成一个单继承链。所以Base2、Base3实际上不是Child的父类

    mixin关键字

    在上面的例子中我们使用普通的class去with,但dart实际上提供了一个mixin关键字,它定义了不能实例化、也不能extends只能with的类:

    mixin Base {
    
    }
    
    // 编译失败: mixin类不能extends
    // class Child extends Base {
    //
    // }
    
    // 编译成功: mixin类可以with
    class Child with Base {
    
    }
    
    void main() {
      // 编译失败: mixin类不能实例化
      // Base()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    这样的类实际上和java、kotlin里面的interface已经很像了。

    另外我们可以通过mixin … on 限定某个类只能由某些类去with:

    class Base1 {
      void foo() {
        print("1");
      }
    }
    
    class Base2 {
      void foo() {
        print("2");
      }
    }
    
    mixin Base3 on Base1 {
      void foo() {
        super.foo();
        print("3");
      }
    }
    
    class Child extends Base1 with Base2, Base3 {}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    上面的demo中Base3只能由Base1去with,那就以为着这个with Base3的类一定是继承或者with了 Base1,所以可以调用这个类的super.foo方法。要注意的是,这个super.foo并不指定一定调用的是Base1.foo。例如上面的代码调用Child().foo()之后的打印实际上是:

    2
    3
    
    • 1
    • 2

    它们线性化的到的继承关系和前面全是class的代码并没有差别:

    img

    从上面的uml图我们就能理解为什么打印是2 3了

    理解了这个简单的例子之后我们再来看一个复杂一点的例子:

    class Base1 {
      void foo1() {
        print("Base1.foo1");
        foo2();
      }
    
      void foo2() {
        print("Base1.foo2");
      }
    }
    
    mixin Base2 on Base1 {
      void foo1() {
        super.foo1();
        print("Base2.foo1");
      }
    
      void foo2() {
        print("Base2.foo2");
      }
    }
    
    class Child with Base1,Base2 {}
    
    void main() {
      Child().foo1();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    它的输出是:

    Base1.foo1
    Base2.foo2
    Base2.foo1
    
    • 1
    • 2
    • 3

    原因是Base2.foo1中的super.foo1实际上调用的是Base1.foo1,而Base1.foo1中的foo2,由于继承的多态特性,调用的是Base2.foo2。

    我们可以通过下面uml图辅助理解,注意看继承关系里面是没有Base1、Base2的因为它们都是通过with混入的,并不是Child的父类:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fLZsiSq3-1659324422994)(https:upload-images.jianshu.io/upload_images/2199790-7482262610cc24c7.png?imageMogr2/auto-orient/strip|imageView2/2/w/758)]

    with的类不能有构造函数

    另外,with的class和mixin类型都是不允许有构造函数的,因为mixin机制语义上是向一个类混入其他类的方法或者成员变量,使得我们可以在混合类中访问到混入类的方法或者属性。而混入其他类的构造函数实际上是没有意义的,因为不会有人手动去调用这个混入类的构造函数。

    class Base1 {
      Base1() {}
    }
    
    // 编译失败: 不能with一个带有构造函数的类
    // class Child with Base1 {}
    
    // 编译失败: mixin类型只能with,所以不能有构造函数
    // mixin Base2 {
    //   Base2() {}
    // }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    参考博客

    https://medium.com/flutter-community/dart-what-are-mixins-3a72344011f3
    https://www.jianshu.com/p/f4efaa6b8fe6
    https://www.jianshu.com/p/fc96bef9beba
    https://my.oschina.net/zzxzzg/blog/2962518

  • 相关阅读:
    初入测试职场如何做好功能测试,学会这4点秒变老鸟...
    《Unity 2D与3D手机游戏开发实战》简介
    Jenkins设置root权限(13)
    js--处理object的常用方法
    【luogu P3295】萌萌哒(并查集)(倍增)
    【论文记录】Boosting Detection in Crowd Analysis via Underutilized Output Features
    mysql8.0删除了权限怎么都还在,是连接工具 navicat 的缓存,删了连接,重新连test 账号,权限就好了
    OpenCV实现图像 开闭运算
    Java中成员变量和局部变量有什么不同呢?
    centos+jenkins+pycharm
  • 原文地址:https://blog.csdn.net/allanGold/article/details/126097801