如何将条件重构为多态对象?

How to refactor a condition into a polymorph object?

我对从条件到多态对象的重构有疑问。我无法理解如何通过使条件始终 return 成为基于类型的数据类型的正确值来将条件概括为多态对象。

我已经尝试过摘要 class 只是我不知道如何进行。 对不起,如果问题是错误的或微不足道的,但我不知道该怎么做。

我的polymorph-model.ts

import { ViewerDataType, XMLImagesValues } from "./models";

export abstract class ViewerDataSource {

    constructor(
        public _type: ViewerDataType
    ){}

    abstract getSource(): string | XMLImagesValues[];
}

export class ManifestSource extends ViewerDataSource {
    getSource(): string {
        return this._type.value.manifestURL
    }
}

export class XMLSource extends ViewerDataSource {
    getSource(): XMLImagesValues[] {
        return this._type.value.xmlImages
    }
}

我的 component.ts 重构的片段。在这里,我需要更改 @InputngAfterViewInit

中的两个条件
//...
public _viewerDataType: string; // tslint:disable-line: variable-name
public _manifestPath: string; // tslint:disable-line: variable-name
public _xmlImagesPath: XMLImagesValues[]; // tslint:disable-line: variable-name
@Input() set data(v: ViewerDataType) {
    // TODO: need to be refactored with polymorph
    if (v.value.manifestURL !== this._manifestPath) {
      this._manifestPath = v.value.manifestURL;
      this.manifestChange.next(this._manifestPath);
    }
    if (v.value.xmlImages !== this._xmlImagesPath) {
      this._xmlImagesPath = v.value.xmlImages;
      this.xmlImageChange.next(this._xmlImagesPath);
    }
    this._viewerDataType = v.type;
  }
  manifestChange = new BehaviorSubject<string>('');
  xmlImageChange = new BehaviorSubject<XMLImagesValues[]>([]);

  ngAfterViewInit() {
    // TODO: need to be refactored with polymorph
    if (this._viewerDataType === 'manifest'){
      this.tileSources = this.manifestTileSources;
    }else{
      this.tileSources = this.xmlImageTileSources;
    }
  }
//...

非常感谢那些会回答的人。

  1. 为了利用您创建的抽象,您首先需要将 set data(...) 方法中接受的数据类型更改为 ViewerDataSource 类型,而不是 [=17] =]:
@Input() set data(v: ViewerDataSource) { }
  1. 这样做可以让您准确检查得到的具体实现,这样您就可以确保将 .getSource() 值分配给适当的 class 属性。这可以通过 JavaScript 的 instanceof operator:
@Input() set data(v: ViewerDataSource) {
  // you can now call .getSource() to retrieve the source
  const source: string | XMLImagesValues[] = v.getSource();

  // if the input value that comes is of type ManifestSource, assign the value 
  // returned by .getSource() to _manifestPath; if the value is of type XMLSource, 
  // assign the .getSource() value to _xmlImagesPath
  if (v instanceof ManifestSource) {
    this._manifestPath = source as string;
    this.manifestChange.next(this._manifestPath);
  } else if (v instanceof XMLSource) {
    this._xmlImagesPath = source as XMLImagesValues[];
    this.xmlImageChange.next(this._xmlImagesPath);
  }

  // finally, assign the type internally
  this._viewerDataType = v._type;
}

如果您这样做,您当前在 ngAfterViewInit() 中的逻辑仍然有效,因为您将 v._type 属性 存储在 _viewerDataType 中,这又是一个字符串联合.如果您想再次在 ngAfterViewInit() 中使用 instanceof,则需要存储作为 data 输入值的 ViewerDataSource 对象,而不是 ViewerDataType 字符串。因此,例如,您的组件中可以有一个 class 属性,名为:

public _viewerDataSource: ViewerDataSource;

然后,在set data(...)方法中你可以用它来存储来的值:

public _viewerDataSource: ViewerDataSource;
@Input() set data(v: ViewerDataSource) {
  const source: string | XMLImagesValues[] = v.getSource();

  if (v instanceof ManifestSource) {
    this._manifestPath = source as string;
    this.manifestChange.next(this._manifestPath);
  } else if (v instanceof XMLSource) {
    this._xmlImagesPath = source as XMLImagesValues[];
    this.xmlImageChange.next(this._xmlImagesPath);
  }

  // note that we are now assigning the object itself
  this._viewerDataSource = v;
}

最后,在 ngAfterViewInit() 中,您可以更改逻辑以使用 instanceof 对抗 this._viewerDataSource:

ngAfterViewInit() {
  if (this._viewerDataSource instanceof ManifestSource) {
    this.tileSources = this.manifestTileSources;
  } else {
    this.tileSources = this.xmlImageTileSources;
  }
}

我想指出 可行,而且我认为不可能有更好的方法。尽管如此,我不喜欢那里的 if 语句,但它们是不可避免的。我会在下面解释自己。

摘要classes

Abstract classes 旨在抽象出其具体实现的实现细节。因此,使用抽象class的组件不需要知道具体的实现是如何工作的,它只需要知道它提供了一个接口(因此抽象class和接口非常相似。)

在您的代码中,您确实依赖于组件中源的确切实现,因为您希望在清单源更改时在 manifestChange() 上发出,类似于 xmlImageChange。因此,您不能抽象出实现细节;您需要了解 XMLSource 的工作原理以及 ManifestSource 的工作原理。摘要 class 不适合这里。

你能做什么?

确保组件不需要知道 ViewDataSource 的实现细节。示例组件:

public _viewerDataType: string; // tslint:disable-line: variable-name
public _manifestPath: string; // tslint:disable-line: variable-name
public _xmlImagesPath: XMLImagesValues[]; // tslint:disable-line: variable-name
@Input() set data(v: ViewerDataSource) {
    this.sourceChange.next(v.getSource());
  }
  sourceChange = new BehaviorSubject<string | XMLImagesValues[]>('');

  async ngAfterViewInit() {
      this.tileSources = await this.sourceChange.pipe(take(1)).toPromise()
  }

您可以将 ManifestSourceXMLSource 作为输入传递。

但现在您不知道类型是 Manifest 还是 XML!所以我们只是移动了这个问题,因为你可能需要在某个地方知道它。

结构变化

我建议你制作一个抽象组件和它的 2 个实现:

abstract class AbstractComponent {
@Input() set data(v: ViewerDataSource) {
    this.sourceChange.next(v.getSource());
  }
  abstract sourceChange: BehaviorSubject<any>;
  abstract titleSources: any;

  async ngAfterViewInit() {
      this.tileSources = await this.sourceChange.pipe(take(1)).toPromise()
  }

  // Do whatever is the same for a manifest and XML here. Since you don't know which one you are going to get, let out any stuff that only applies to one of the two!
}
@Component({
   blablabla...
})
class ManifestComponent extends AbstractComponent {
    public manifestPath: string;
    public sourceChanges = new BehaviourSubject<string>('');
    public titleSources: string;

   // Do whatever you need to do with a manifest here. You are sure the type is a manifest in this component, so you do not need to check it!
}
@Component({
   blablabla...
})
class XMLComponent extends AbstractComponent {
    public xmlImagesPath: XMLImagesValues[];;
    public sourceChanges = new BehaviourSubject<XMLImagesValues[]>([]);
    public titleSources: XMLImagesValues[];

   // Do whatever you need to do with XML image values here. You are sure the type XML in this component, so you do not need to check it!
}

现在您可以使用 XMLComponentManifestComponent 并且您不需要任何 if 来检查它是哪种类型,因为您已经知道它了!