Typescript - 开闭原则

Typescript - Open-closed principle

我想在我的 "small" 项目中使用 OCP 规则。实际上我在实施这个问题时遇到了问题。让我告诉你我写了什么。

abstract class AnimationType {
    public abstract createAnimation(timing: number): { in: string, out: string };
}

class FadingAnimation extends AnimationType {
  public createAnimation(timing: number): { in: string, out: string } {
    return { in: 'opacity: 0', out: 'opacity: 1' }
  }
}

class TransformAnimation extends AnimationType {
  public createAnimation(timing: number): { in: string, out: string } {
    return { in: 'transform: translateX(-100%)', out: 'transform: translateX(100%)' }
  }
}

class AnimationCreator {
  private timing: number;

  constructor(time: number) {
    this.timing = time;
  }

  getAnimation(animation: AnimationType): { in: string, out: string } {
    return animation.createAnimation(this.timing);
  }
}

我写的好吗?如果我这样做了,那我究竟得到了什么?如果我想添加这个动画,我需要发送 FadingAnimation class 的对象。如果我想要变换动画,我需要发送 TransformAnimation 对象 class - 所以我必须在某个地方使用 switch(this.animationType).

我同意 Aluan Haddad 的观点。不要在这里想简单的 JAVA-Patterns。尽管 Typescript 看起来很像 C#,因此甚至有点像 JAVA,但它最终仍然是 JavaScript。正如您从 JAVA 了解到的那样,继承规则在这里并不适用。不要试图建立复杂的继承模式或相关的设计原则。使用 Typescript 你必须调整你的编码方式。

解决您的问题的一个好方法是使用具有 2 个方法的普通服务,return 需要的对象。

例如

import { Injectable } from '@angular/core';

@Injectable()
export class AnimationTypeService {

    constructor() {}

    getFadingAnimation(timing: number): { in: string, out: string } {
        return { in: 'opacity: 0', out: 'opacity: 1' };
    }

    getTransformAnimation(timing: number): { in: string, out: string } {
        return { in: 'transform: translateX(-100%)', out: 'transform: translateX(100%)' };
    }

}

上面的代码实际上并不需要面向对象。 AnimationType class 没有状态,无法从多个实例中受益,因此不需要。除非 class 个实例用 animation instanceof TransformAnimation 区分(这不是开闭原则应该做的事情),否则这些 classes.

是没有用的

AnimationType class可以用函数式的方式代替:

interface IAnimation {
  type?: string;
  in: string;
  out: string;
}

type TAnimationFactory = (timing: number) => IAnimation;

const createFadeAnimation: TAnimationFactory = (timing: number) => {
   return { in: 'opacity: 0', out: 'opacity: 1' };
}

class AnimationCreator {
  private timing: number;

  constructor(time: number) {
    this.timing = time;
  }

  getAnimation(animationFactory: TAnimationFactory): IAnimation {
    return animationFactory(this.timing);
  }
}

根据AnimationCreator的作用,它也可能被一个函数取代,或者被废除以支持直接使用动画工厂函数。

如果 AnimationType 是有状态的,例如它接受 transform % 作为构造函数参数。但在这种情况下,不清楚为什么 transform 属性 应该被视为与 timing 不同的类型 - 两者都可能因动画而异,因此在构造时定义。在这种情况下 AnimationCreator 不需要 timing,因此它是无状态的,其目的变得不明确。

原则和模式绝对可以在语言之间转移。当您跨越范式的界限时,它们可能不太适用。 SOLID 原则和 OOP 模式适用于任何面向对象,而不仅仅是 Java.

请记住 TypeScript/JavaScript 不仅仅是面向对象的。

从问题开始

问题不在于您试图借鉴来自其他语言的原则。这里的问题是你试图从原理入手。

好的代码设计,无论您使用什么范式,都始于您想要解决的问题。在寻找模式或应用原则之前编写更多代码,这样设计就可以从您在应用程序增长时发现的实际问题中浮现出来。

从一个原则开始,你就过早地决定问题最终可能是什么;并且您的设计可能会使您离简洁代码的目标更远。

模式和原则是高度可移植的,但等待出现问题的信号,而不是过早地应用一些解决方案。

越简单越好

你的情况,用最简单的方法就可以解决问题。如果你有一个测试来指导你,你可以在以后轻松更新实现细节。

也许在您的情况下,一个简单的 dictionary/object 就足够了。

const animationType = {
    fading: {
        in: 'opacity: 0',
        out: 'opacity: 1'
    },
    transforming: {
        in: 'transform: translateX(-100%)',
        out: 'transform: translateX(100%)'
    }
};

如果您不想强制执行 fadingtransforming,您可以键入此内容以允许任何密钥 - 但您还不需要添加此灵活性:

interface AnimationTypes {
    [key: string]: { in: string, out: string };
}