如何重构两种不同类型的 sub 类 的实现,同时保留其方法的功能和命名?
How could one refactor the implementation of two different types of sub classes while keeping both, functionality and naming of their methods?
我有一个基础抽象 class 需要扩展,它定义了一个唯一的 public 方法,该方法使用子 class 期望的功能。在这里,只有 fullSoundSentence
是“public” - 供外界使用。
class Animal {
constructor(nickname){
this.nickname = nickname
}
fullSoundSentence(){
//expects this.composesound to exist
return `${this.nickname} ${this.composesound()}!`
}
}
然后我有许多 classes 扩展它提供一些核心功能(这里他们只是 return 一个字符串但实际上他们需要访问 this
,修改对象的属性等等)
class Dog extends Animal {
doessound(){
return "barks"
}
}
class Cat extends Animal {
doessound(){
return "meows"
}
}
另一个子系列classes 然后以不同的方式使用此功能。有点儿
我们使用 Dog
和 Cat
功能的“模式”。这些模式提供了抽象基方法(fullSoundSentence
)所期望的功能,但它们需要是特定的动物。
class LoudAnimal extends Animal {
composesound(){
return `loudly ${this.doessound()}`
}
}
class CalmAnimal extends Animal {
composesound(){
return `calmly ${this.doessound()}`
}
}
现在,猫和狗都可以既安静又大声。 = 任何动物都可以处于任何模式
问:如何创建平静的狗或吵闹的猫等等?
我可以为每个组合手动创建 classes (CalmDog
, LoudDog
, CalmCat
, ...) 但如果有更多的动物和更多的模式,这很糟糕
我可以使模式只是具有抽象 Animal
期望作为参数的功能的对象,但这很混乱,特别是如果模式还需要修改属性和等等
没有更好的方法吗?? 我正在寻找在 JavaScript 或 TypeScript 中执行此操作的方法。
下一个提供的方法引入了一个工具集 (a) 上下文感知函数和基于函数的 mixin (它实现了无法以有意义的方式提供的行为通过子类型 class-hierarchy) 和 (b) 一个 Animal
基础 class,一些更具体的动物子类型和工厂,以表明基于混合的组合可以在对象创建过程(或对象的生命周期)的任何时间和任何级别应用。
所有实现的功能都源自 OP 的示例代码,分解并重新组装,同时保持特定的动物类型 属性 和方法名称。
来自OP最初提供的Animal
和Dog/Cat extends Animal
代码,为了证明基数Animal
class的存在,也是为了DRY 两个子类型(Dog
和 Cat
),这样的基本类型不仅要有它的 nickname
还要有它的 sound
属性 及其附带的 doessound
方法。 Dog
和 Cat
都依赖于 super
委派并且足以提供默认值。
剩下的取决于进一步子类型和混合组合的混合,或者例如一个实例化 class 并通过混合组合将特定行为应用于实例的工厂。
这是为什么?
两行代码...
class LoudAnimal extends Animal { composesound () { /* ... */ } }
...和...
class CalmAnimal extends Animal { composesound () { /* ... */ } }
...完全没有意义。
calm/loud 动物类型不能should/could 以有意义的方式描述,例如具有 nickname
和 sound
属性 和 doessound
方法,而 dog/cat 可以通过这些动物特定键来描述和识别。
但是可以有吵闹的狗 and/or 平静的猫 and/or 反之亦然。因此,此类类型需要获得特定的 behavior/traits,例如 calm/loud 或表达自身 calmly/loudly。这就是 mixins 的用途。
// sound-specific functions/methods (behavior) utilized by mixins.
function getBoundBehaviorDrivenSound(behavior) {
// expects `doessound` to exist but does
// handle it forgiving via optional chaining.
return `${ behavior } ${ this?.doessound?.() }`;
}
function getFullSound() {
// expects `nickname` and `composesound` to exist but
// handles the latter forgiving via optional chaining.
return `${ this.nickname } ${ this?.composesound?.() }!`;
}
// sound-specific function-based mixins (applicable behavior).
function withSoundingLoud() {
this.composesound =
getBoundBehaviorDrivenSound.bind(this, 'loudly');
return this;
}
function withSoundingCalm() {
this.composesound =
getBoundBehaviorDrivenSound.bind(this, 'calmly');
return this;
}
function withFullSound() {
this.fullSoundSentence = getFullSound;
return this;
}
// base-class and sub-typing
class Animal {
constructor({
nickname = 'beast',
sound = 'grmpf',
}) {
Object.assign(this, { nickname, sound });
}
doessound() {
return this.sound;
}
}
class Dog extends Animal {
constructor(nickname = 'Buster') {
super({ nickname, sound: 'barks' });
}
}
class Cat extends Animal {
constructor(nickname = 'Chloe') {
super({ nickname, sound: 'meows' });
}
}
// further sub-typing and mixin based composition.
class LoudDog extends Dog {
constructor(nickname) {
super(nickname);
// mixin at creation time at instance/object level.
withSoundingLoud.call(this);
// withFullSound.call(this);
}
}
// factory function featuring mixin based composition.
function createFullySoundingCalmCat(nickname) {
// mixin at creation time at instance/object level.
return withFullSound.call(
withSoundingCalm.call(
new Cat(nickname)
)
);
}
const smokey = createFullySoundingCalmCat('Smokey');
const cooper = new LoudDog('Cooper');
console.log({ smokey, cooper });
console.log('smokey.doessound() ...', smokey.doessound());
console.log('cooper.doessound() ...', cooper.doessound());
console.log('smokey.composesound() ...', smokey.composesound());
console.log('cooper.composesound() ...', cooper.composesound());
console.log('smokey.fullSoundSentence() ...', smokey.fullSoundSentence());
console.log('cooper?.fullSoundSentence?.() ...', cooper?.fullSoundSentence?.());
// ... one more ...
class FullySoundingLoudDog extends LoudDog {
constructor(nickname) {
super(nickname);
}
}
// prototype level aggregation / "class level mixin".
withFullSound.call(FullySoundingLoudDog.prototype);
const anotherDog = new FullySoundingLoudDog;
console.log({ anotherDog });
console.log('anotherDog.doessound() ...', anotherDog.doessound());
console.log('anotherDog.composesound() ...', anotherDog.composesound());
console.log('anotherDog.fullSoundSentence() ...', anotherDog.fullSoundSentence());
.as-console-wrapper { min-height: 100%!important; top: 0; }
我有一个基础抽象 class 需要扩展,它定义了一个唯一的 public 方法,该方法使用子 class 期望的功能。在这里,只有 fullSoundSentence
是“public” - 供外界使用。
class Animal {
constructor(nickname){
this.nickname = nickname
}
fullSoundSentence(){
//expects this.composesound to exist
return `${this.nickname} ${this.composesound()}!`
}
}
然后我有许多 classes 扩展它提供一些核心功能(这里他们只是 return 一个字符串但实际上他们需要访问 this
,修改对象的属性等等)
class Dog extends Animal {
doessound(){
return "barks"
}
}
class Cat extends Animal {
doessound(){
return "meows"
}
}
另一个子系列classes 然后以不同的方式使用此功能。有点儿
我们使用 Dog
和 Cat
功能的“模式”。这些模式提供了抽象基方法(fullSoundSentence
)所期望的功能,但它们需要是特定的动物。
class LoudAnimal extends Animal {
composesound(){
return `loudly ${this.doessound()}`
}
}
class CalmAnimal extends Animal {
composesound(){
return `calmly ${this.doessound()}`
}
}
现在,猫和狗都可以既安静又大声。 = 任何动物都可以处于任何模式
问:如何创建平静的狗或吵闹的猫等等?
我可以为每个组合手动创建 classes (
CalmDog
,LoudDog
,CalmCat
, ...) 但如果有更多的动物和更多的模式,这很糟糕我可以使模式只是具有抽象
Animal
期望作为参数的功能的对象,但这很混乱,特别是如果模式还需要修改属性和等等
没有更好的方法吗?? 我正在寻找在 JavaScript 或 TypeScript 中执行此操作的方法。
下一个提供的方法引入了一个工具集 (a) 上下文感知函数和基于函数的 mixin (它实现了无法以有意义的方式提供的行为通过子类型 class-hierarchy) 和 (b) 一个 Animal
基础 class,一些更具体的动物子类型和工厂,以表明基于混合的组合可以在对象创建过程(或对象的生命周期)的任何时间和任何级别应用。
所有实现的功能都源自 OP 的示例代码,分解并重新组装,同时保持特定的动物类型 属性 和方法名称。
来自OP最初提供的Animal
和Dog/Cat extends Animal
代码,为了证明基数Animal
class的存在,也是为了DRY 两个子类型(Dog
和 Cat
),这样的基本类型不仅要有它的 nickname
还要有它的 sound
属性 及其附带的 doessound
方法。 Dog
和 Cat
都依赖于 super
委派并且足以提供默认值。
剩下的取决于进一步子类型和混合组合的混合,或者例如一个实例化 class 并通过混合组合将特定行为应用于实例的工厂。
这是为什么?
两行代码...
class LoudAnimal extends Animal { composesound () { /* ... */ } }
...和...
class CalmAnimal extends Animal { composesound () { /* ... */ } }
...完全没有意义。
calm/loud 动物类型不能should/could 以有意义的方式描述,例如具有 nickname
和 sound
属性 和 doessound
方法,而 dog/cat 可以通过这些动物特定键来描述和识别。
但是可以有吵闹的狗 and/or 平静的猫 and/or 反之亦然。因此,此类类型需要获得特定的 behavior/traits,例如 calm/loud 或表达自身 calmly/loudly。这就是 mixins 的用途。
// sound-specific functions/methods (behavior) utilized by mixins.
function getBoundBehaviorDrivenSound(behavior) {
// expects `doessound` to exist but does
// handle it forgiving via optional chaining.
return `${ behavior } ${ this?.doessound?.() }`;
}
function getFullSound() {
// expects `nickname` and `composesound` to exist but
// handles the latter forgiving via optional chaining.
return `${ this.nickname } ${ this?.composesound?.() }!`;
}
// sound-specific function-based mixins (applicable behavior).
function withSoundingLoud() {
this.composesound =
getBoundBehaviorDrivenSound.bind(this, 'loudly');
return this;
}
function withSoundingCalm() {
this.composesound =
getBoundBehaviorDrivenSound.bind(this, 'calmly');
return this;
}
function withFullSound() {
this.fullSoundSentence = getFullSound;
return this;
}
// base-class and sub-typing
class Animal {
constructor({
nickname = 'beast',
sound = 'grmpf',
}) {
Object.assign(this, { nickname, sound });
}
doessound() {
return this.sound;
}
}
class Dog extends Animal {
constructor(nickname = 'Buster') {
super({ nickname, sound: 'barks' });
}
}
class Cat extends Animal {
constructor(nickname = 'Chloe') {
super({ nickname, sound: 'meows' });
}
}
// further sub-typing and mixin based composition.
class LoudDog extends Dog {
constructor(nickname) {
super(nickname);
// mixin at creation time at instance/object level.
withSoundingLoud.call(this);
// withFullSound.call(this);
}
}
// factory function featuring mixin based composition.
function createFullySoundingCalmCat(nickname) {
// mixin at creation time at instance/object level.
return withFullSound.call(
withSoundingCalm.call(
new Cat(nickname)
)
);
}
const smokey = createFullySoundingCalmCat('Smokey');
const cooper = new LoudDog('Cooper');
console.log({ smokey, cooper });
console.log('smokey.doessound() ...', smokey.doessound());
console.log('cooper.doessound() ...', cooper.doessound());
console.log('smokey.composesound() ...', smokey.composesound());
console.log('cooper.composesound() ...', cooper.composesound());
console.log('smokey.fullSoundSentence() ...', smokey.fullSoundSentence());
console.log('cooper?.fullSoundSentence?.() ...', cooper?.fullSoundSentence?.());
// ... one more ...
class FullySoundingLoudDog extends LoudDog {
constructor(nickname) {
super(nickname);
}
}
// prototype level aggregation / "class level mixin".
withFullSound.call(FullySoundingLoudDog.prototype);
const anotherDog = new FullySoundingLoudDog;
console.log({ anotherDog });
console.log('anotherDog.doessound() ...', anotherDog.doessound());
console.log('anotherDog.composesound() ...', anotherDog.composesound());
console.log('anotherDog.fullSoundSentence() ...', anotherDog.fullSoundSentence());
.as-console-wrapper { min-height: 100%!important; top: 0; }