如何重构两种不同类型的 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 然后以不同的方式使用此功能。有点儿 我们使用 DogCat 功能的“模式”。这些模式提供了抽象基方法(fullSoundSentence)所期望的功能,但它们需要是特定的动物。

class LoudAnimal extends Animal {
    composesound(){
        return `loudly ${this.doessound()}`
    }
}
class CalmAnimal extends Animal {
    composesound(){
        return `calmly ${this.doessound()}`
    }
}

现在,猫和狗都可以既安静又大声。 = 任何动物都可以处于任何模式

问:如何创建平静的狗或吵闹的猫等等?

没有更好的方法吗?? 我正在寻找在 JavaScript 或 TypeScript 中执行此操作的方法。

下一个提供的方法引入了一个工具集 (a) 上下文感知函数和基于函数的 mixin (它实现了无法以有意义的方式提供的行为通过子类型 class-hierarchy)(b) 一个 Animal 基础 class,一些更具体的动物子类型和工厂,以表明基于混合的组合可以在对象创建过程(或对象的生命周期)的任何时间和任何级别应用。

所有实现的功能都源自 OP 的示例代码,分解并重新组装,同时保持特定的动物类型 属性 和方法名称。

来自OP最初提供的AnimalDog/Cat extends Animal代码,为了证明基数Animal class的存在,也是为了DRY 两个子类型(DogCat),这样的基本类型不仅要有它的 nickname 还要有它的 sound 属性 及其附带的 doessound 方法。 DogCat 都依赖于 super 委派并且足以提供默认值。

剩下的取决于进一步子类型和混合组合的混合,或者例如一个实例化 class 并通过混合组合将特定行为应用于实例的工厂。

这是为什么?

两行代码...

class LoudAnimal extends Animal { composesound () { /* ... */ } }

...和...

class CalmAnimal extends Animal { composesound () { /* ... */ } }

...完全没有意义。

calm/loud 动物类型不能should/could 以有意义的方式描述,例如具有 nicknamesound 属性 和 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; }