如何将条件重构为多态对象?
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
重构的片段。在这里,我需要更改 @Input
和 ngAfterViewInit
中的两个条件
//...
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;
}
}
//...
非常感谢那些会回答的人。
- 为了利用您创建的抽象,您首先需要将
set data(...)
方法中接受的数据类型更改为 ViewerDataSource
类型,而不是 [=17] =]:
@Input() set data(v: ViewerDataSource) { }
- 这样做可以让您准确检查得到的具体实现,这样您就可以确保将
.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()
}
您可以将 ManifestSource
和 XMLSource
作为输入传递。
但现在您不知道类型是 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!
}
现在您可以使用 XMLComponent
和 ManifestComponent
并且您不需要任何 if 来检查它是哪种类型,因为您已经知道它了!
我对从条件到多态对象的重构有疑问。我无法理解如何通过使条件始终 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
重构的片段。在这里,我需要更改 @Input
和 ngAfterViewInit
//...
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;
}
}
//...
非常感谢那些会回答的人。
- 为了利用您创建的抽象,您首先需要将
set data(...)
方法中接受的数据类型更改为ViewerDataSource
类型,而不是 [=17] =]:
@Input() set data(v: ViewerDataSource) { }
- 这样做可以让您准确检查得到的具体实现,这样您就可以确保将
.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;
}
}
我想指出
摘要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()
}
您可以将 ManifestSource
和 XMLSource
作为输入传递。
但现在您不知道类型是 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!
}
现在您可以使用 XMLComponent
和 ManifestComponent
并且您不需要任何 if 来检查它是哪种类型,因为您已经知道它了!