在Flutter中,什么时候应该更喜欢'Widget'继承而不是组合?

5sxhfpxr  于 2022-11-25  发布在  Flutter
关注(0)|答案(2)|浏览(120)

我一直在读this post之类的东西,解释Flutter为什么更喜欢组合而不是继承。虽然我部分理解了其中的原因,但我怀疑在这种做法变得冗长的情况下该怎么办。另外,在Flutter的内部代码中,内置组件到处都是继承。所以从哲学上讲,一定有这样的情况。
考虑这个例子(基于我制作的真实Widget):

class MyFadingAnimation extends StatefulWidget {
    final bool activated;
    final Duration duration;
    final Curve curve;
    final Offset transformOffsetStart;
    final Offset transformOffsetEnd;
    final void Function()? onEnd;
    final Widget? child;

    const MyFadingAnimation({
        super.key,
        required this.activated,
        this.duration = const Duration(milliseconds: 500),
        this.curve = Curves.easeOut,
        required this.transformOffsetStart,
        this.transformOffsetEnd = const Offset(0, 0),
        this.onEnd,
        this.child,
    });

    @override
    State<MyFadingAnimation> createState() => _MyFadingAnimationBuilder();
}

class _MyFadingAnimationBuilder extends State<MyFadingAnimation> {
    @override
    Widget build(BuildContext context) {
        return AnimatedContainer(
            duration: widget.duration,
            curve: widget.curve,
            transform: Transform.translate(
                offset: widget.activated ?
                    widget.transformOffsetStart : widget.transformOffsetEnd,
            ).transform,
            onEnd: widget.onEnd,
            child: AnimatedOpacity(
                duration: widget.duration,
                curve: widget.curve,
                opacity: widget.activated ? 1 : 0,
                child: widget.child
            ),
        );
    }
}

MyFadingAnimation的目标是在Widget上同时执行平移和不透明度动画。太棒了!
现在,假设我想为这个小部件创建一些“快捷方式”或“别名”,比如水平淡入的MyHorizontalAnimation,或者垂直淡入的MyVerticalAnimation

class MyHorizontalAnimation extends StatelessWidget {
    final bool activated;
    final Duration duration;
    final Curve curve;
    final double offsetStart;
    final void Function()? onEnd;
    final Widget? child;

    const MyHorizontalAnimation({
        super.key,
        required this.activated,
        this.duration = const Duration(milliseconds: 500),
        this.curve = Curves.easeOut,
        required this.offsetStart,
        this.onEnd,
        this.child,
    });

    @override
    Widget build(BuildContext context) {
        return MyFadingAnimation(
            activated: activated,
            duration: duration,
            curve: curve,
            transformOffsetStart: Offset(offsetStart, 0),
            onEnd: onEnd,
            child: child,
        );
    }
}

这对我来说似乎...非常冗长。所以我最初的想法是“好吧,也许我无论如何都应该试着扩展这个类...”

class MyHorizontalAnimation extends MyFadingAnimation {
    final double offsetStart;

    MyHorizontalAnimation({
        super.key,
        required super.activated,
        super.duration,
        super.curve,
        this.offsetStart,
        super.onEnd,
        super.child,
    }) : super(
        transformOffsetStart: Offset(offsetStart, 0),
    );
}

对我来说,这看起来更干净。另外,它还带来了额外的好处,如果我向MyFadingAnimation添加功能/道具,它 * 几乎 * 会自动集成到MyHorizontalAnimation中(除了必须添加super.newProp)。使用组合方法,我必须添加一个新属性,可能要复制/维护一个默认值,将其添加到构造函数中,等我做完的时候感觉就像是个苦差事。
我使用继承的主要问题是(这可能真的很小),除了我的基本部件MyFadingAnimation之外,我不能为任何东西使用const构造函数。这一点,再加上继承的 * 强烈 * 阻碍,让我觉得有更好的方法。
所以,总结一下,我有两个问题:
1.我应该如何组织上面的代码,使constWidget重定向到其他“基”Widget
1.什么时候可以使用继承而不是组合?对此是否有一个好的经验法则?

xwbd5t1u

xwbd5t1u1#

我不会担心重定向构造函数中缺少const a-毕竟,组合示例内部的MyFadingAnimation构造中也缺少const。不可能用未知的整数参数生成const Offset,所以这是不可避免的语言限制。
关于组合与继承的主题,对于您的用例还有另一种解决方案:基类中的辅助构造函数。这个模式在整个框架中都使用-例如,看SizedBox
但是,请注意,当涉及到默认参数值时,这种样式确实会引入一些重复性。

class MyFadingAnimation extends StatefulWidget {
    final bool activated;
    final Duration duration;
    final Curve curve;
    final Offset transformOffsetStart;
    final Offset transformOffsetEnd;
    final void Function()? onEnd;
    final Widget? child;

    const MyFadingAnimation({
        super.key,
        required this.activated,
        this.duration = const Duration(milliseconds: 500),
        this.curve = Curves.easeOut,
        required this.transformOffsetStart,
        this.transformOffsetEnd = const Offset(0, 0),
        this.onEnd,
        this.child,
    });

    MyFadingAnimation.horizontal({
      super.key,
      required this.activated,
      this.duration = const Duration(milliseconds: 500),
      this.curve = Curves.easeOut,
      required double offsetStart,
      this.onEnd,
      this.child,
    })  : transformOffsetStart = Offset(offsetStart, 0),
          transformOffsetEnd = const Offset(0, 0);

    @override
    State<MyFadingAnimation> createState() => _MyFadingAnimationBuilder();
}
pgvzfuti

pgvzfuti2#

为什么要使用组合而不是继承?

优化

正如您所提到的,当使用composition时,外部小部件可以提供一个const构造函数,在build中执行计算,而不是在构造函数本身中。这在许多方面都很有用:

  1. const具有传染性,任何使用你的widget的widget都可以声明为const,这会给大widget树带来巨大的好处。
    1.除非实际使用Widget,否则不会执行任何计算。
    以一个在两个子部件之间交叉渐变的部件为例,有时候一个子部件永远不会被构建出来,在build方法中执行计算就可以构建一个可能永远不会被使用的子部件。

类型无关紧要

Flutter风格指南规定如下:
每个API都应该是自包含的,并且不应该知道其他功能。
许多小部件都接受child。小部件应该完全不知道子部件的类型。不要使用is或类似的检查来根据子部件的类型采取不同的操作。
例如,许多具有text字段的小部件接受任何Widget,而不是Text小部件。
如果遵循这条指令,继承的最大特性之一就变得无关紧要了:继承的公共API。2小部件不应该(通常)被用于除了被传递和返回之外的任何事情。3有什么理由使用继承呢?

灵活

使用继承只允许使用一个单独的、特定类型的子控件。如果您希望以后使重定向小部件变得更复杂,该怎么办?
例如,您可能意识到HorizontalFadingAnimation可以使用一些添加的动画填充来获得更好的视觉效果。
将子小部件 Package 在另一个小部件中需要完全重写外部小部件,以及中断的API更改(因为小部件的类型必须更改)。

相关问题