如何在不使用继承的情况下实现可应用于多个不同类的通用功能?

huangapple go评论74阅读模式
英文:

How would one implement generic functionality which gets applied across various distinct classes without inheritance?

问题

我需要定义一个可以被多个类使用的函数,但据我了解,从超类继承对我来说行不通。基本上,我想要实现的是为每个类扩展多个接口的能力。

例如,如果我定义了类 AppleOrangeBanana,我希望它们都有一个相同的 isFresh() 函数。我还想让 AppleOrangeEarth 具有一个 getRadius() 方法。这有点类似于 Apple interface Fruit, SphericalObject {...}。我也希望能够在需要时覆盖这些函数。但是,继承对我来说行不通,因为我想要从多个超类继承。

实现这个的最佳方式是什么?

我知道有一个类似的帖子,我理解从中得知 JavaScript 是动态类型的,没有接口,而建议的鸭子类型似乎不能解决我的问题。我并不在乎检查接口中的方法是否存在于子类中。

英文:

I need to define a function that can be used by multiple classes, but as far as I understand, inheriting from a superclass doesn't work for me. Essentially, what I would like to achieve is the ability to extend multiple interfaces for each class.

For example, if I have defined classes Apple, Orange, Banana, I want all of them to have an identical isFresh() function. I also like to let Apple, Orange, and Earth to have a getRadius() method. This is somewhat similar to Apple interface Fruit, SphericalObject {...} I also want to be able to override the functions if I want to. However, inheritance doesn't work for me because I would like to inherit from multiple superclasses.

What is the best way to achieve this?

I am aware of this similar post, I understand from that JavaScript is dynamically typed and does not have interfaces, and the suggested Duck Type doesn't seem to solve my problem. I don't really care to check if the method in interface exist in child classes.

答案1

得分: 1

以下是您要翻译的代码部分:

"Looks like you're looking for 'mixins'. They are not built-in in javascript, but are quite easy to implement in userland, for example:

function augment(cls, ...mixins) {
    return class extends cls {
        constructor(...args) {
            super(...args)

            for (let c of mixins)
                for (let p of Object.getOwnPropertyNames(c.prototype))
                    if (p !== 'constructor')
                        this[p] = c.prototype[p]
        }
    }
}

//

class Apple {}

class PhysicalObject {
    isFresh() {
        return 'hey'
    }
}

let AppleWithObject = augment(Apple, PhysicalObject)
let x = new AppleWithObject()
console.log(x.isFresh())

"

英文:

Looks like you're looking for "mixins". They are not built-in in javascript, but are quite easy to implement in userland, for example:

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

function augment(cls, ...mixins) {
    return class extends cls {
        constructor(...args) {
            super(...args)

            for (let c of mixins)
                for (let p of Object.getOwnPropertyNames(c.prototype))
                    if (p !== &#39;constructor&#39;)
                        this

= c.prototype

} } } // class Apple {} class PhysicalObject { isFresh() { return &#39;hey&#39; } } let AppleWithObject = augment(Apple, PhysicalObject) let x = new AppleWithObject() console.log(x.isFresh())

<!-- end snippet -->

答案2

得分: 0

你只需要一个单独的 extends 来实现你的结果。

class PhysicalObject {
   constructor(x, y) { this.x = x; this.y = y; }
   getPosition() { return { x: this.x, y: this.y } }
   displayPosition() { console.log(this.getPosition().x + ', ' + this.getPosition().y) }
}

Earth = new PhysicalObject(0, 0);
Earth.displayPosition();

class Fruit extends PhysicalObject {
  constructor(x, y, a) { super(x, y); this.age = a; }
  isFresh() { return this.age < 7 }
}

Apple = new Fruit(1, 1, 6);
Apple.displayPosition();
console.log(Apple.isFresh());
英文:

You only needs a single extends to achieve your result.

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

class PhysicalObject {
   constructor(x,y) {this.x=x;this.y=y;}
   getPosition() {return {x:this.x,y:this.y}}
   displayPosition() {console.log(this.getPosition().x+&#39;, &#39;+this.getPosition().y)}
   }
   
Earth=new PhysicalObject(0,0);
Earth.displayPosition();

class Fruit extends PhysicalObject {
  constructor(x,y,a) {super(x,y);this.age=a;}
  isFresh() {return this.age&lt;7}
  }
  
Apple=new Fruit(1,1,6);
Apple.displayPosition();
console.log(Apple.isFresh());

<!-- end snippet -->

答案3

得分: 0

以下是您要翻译的内容:

关于可能会被广泛误解的风险:受 Douglas Crockford 的启发,我停止使用类或原型(嗯,类在 ES 中我从未使用过,从未有过任何用途)。

相反,我创建 工厂函数。这里是一个示例性的水果工厂。

为了尝试这个想法,我创建了一个小的 Stackblitz 项目,采用更通用的方法。

以下是代码片段的一部分:

const FruitStore = FruitFactory();

FruitStore.apple = { 
  mustPeal: false, color: `red`, fresh: "Nope", origin: `Guatamala`, 
  inheritsFrom: {
    ...PhysicalObjectFactory(true), 
    ...ChemicalObjectFactory(true, null, true) },
};
FruitStore.orange = { inheritsFrom: {
  origin: `Spain`, fresh: false, color: `orange` } 
};
FruitStore.pineapple = { color: `yellow`, spherical: false, qty: `200Kg` };
console.log(FruitStore.all);
FruitStore.orange.fresh = `UNKNOWN`;
console.log(FruitStore.orange);

function PhysicalObjectFactory(spherical) {
  return { isPhysical: true, isSpherical: spherical };
}

function ChemicalObjectFactory(
  carbonBased = null, boilingPoint = null, solid = null) {
  return { carbonBased, boilingPoint, solid };
}

function FruitFactory() {
  let allFruits = {};
  // 所有水果都“继承”这些属性
  // 您可以在创建水果实例时覆盖其中任何一个
  const fruitDefaults = {
    mustPeel: true,
    fresh: true,
    qty: `unset`,
  };
  const instance = { get all() { return allFruits; } };
  // 代理确保您在 `allFruits` 实例上工作
  const proxy = { 
    get (obj, key) { return allFruits[key] ?? obj[key]; },
    set(_, key, props) { 
      allFruits[key] = createFruitInstance(key, props); 
      return true; 
    },
  };

  return new Proxy(instance, proxy);

  function createFruitInstance(name, props = {}) {
    const fruit = { name };
    let inherits = {};
    let inheritsFrom = { ...props.inheritsFrom };
    delete props.inheritsFrom;
    Object.entries({...fruitDefaults, ...props, ...inheritsFrom})
    .forEach( ([key, value]) => 
      value || key in fruitDefaults ? fruit[key] = value : false 
    );
    return fruit;
  }
}

希望这对您有所帮助。

英文:

On the risk of being misprised extensively: inspired by Douglas Crockford I stopped using classes or prototypes (well, classes I never used in ES, never had any use for it).

Instead I create factory functions. Here's an examplary Fruit factory.

To play with the idea I created a small Stackblitz project, with a more generic approach.

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

const FruitStore = FruitFactory();
FruitStore.apple = { 
mustPeal: false, color: `red`, fresh: &quot;Nope&quot;, origin: `Guatamala`, 
inheritsFrom: {
...PhysicalObjectFactory(true), 
...ChemicalObjectFactory(true, null, true) },
};
FruitStore.orange = { inheritsFrom: {
origin: `Spain`, fresh: false, color: `orange` } 
};
FruitStore.pineapple = { color: `yellow`, spherical: false, qty: `200Kg` };
console.log(FruitStore.all);
FruitStore.orange.fresh = `UNKNOWN`;
console.log(FruitStore.orange);
function PhysicalObjectFactory(spherical) {
return { isPhysical: true, isSpherical: spherical };
}
function ChemicalObjectFactory(
carbonBased = null, boilingPoint = null, solid = null) {
return { carbonBased, boilingPoint, solid };
}
function FruitFactory() {
let allFruits = {};
// all fruits &#39;inherit&#39; these properties
// you can override any of them on
// creating a fruit instance
const fruitDefaults = {
mustPeel: true,
fresh: true,
qty: `unset`,
};
const instance = { get all() { return allFruits; }, };
// the proxy ensures you&#39;re working on the `allFruits` instance
const proxy = { 
get (obj, key) { return allFruits[key] ?? obj[key]; },
set(_, key, props) { 
allFruits[key] = createFruitInstance(key, props); 
return true; 
},
};
return new Proxy(instance, proxy);
function createFruitInstance(name, props = {}) {
const fruit = { name };
let inherits = {};
let inheritsFrom = { ...props.inheritsFrom };
delete props.inheritsFrom;
Object.entries({...fruitDefaults, ...props, ...inheritsFrom})
.forEach( ([key, value]) =&gt; 
value || key in fruitDefaults ? fruit[key] = value : false 
);
return fruit;
}
}

<!-- language: lang-css -->

.as-console-wrapper {
max-height: 100% !important;
}

<!-- end snippet -->

答案4

得分: 0

以下是您要翻译的内容:

受@gog的答案启发,我想分享一个对我更有效的修改版本。这个解决方案

  1. 避免定义一个最终不会使用的临时类(例如gog答案中的空的Apple类),
  2. 是在“超类”中初始化变量的一种清晰方式(使用gog的原始代码,我无法找到在“超类”中定义和继承变量的清晰方式,这使得如果我想在“子类”中定义使用这些变量的函数,它就会变得“不安全”。
function augment(ins, ...mixins) {
    for (let c of mixins)
        for (let p of Object.getOwnPropertyNames(c.prototype))
            if (p !== 'constructor')
                ins[p] = c.prototype[p]
}

class Alice {
  initAlice() {
    this.name = 'Alice';
  }
}

class Teacher {
  initTeacher() {
    this.occupation = 'Teacher';
  }
}

class RealAlice {
  constructor() {
    augment(this, Alice, Teacher);
    this.initAlice();
    this.initTeacher();
  }
}

const alice = new RealAlice(30);
console.log(alice.name); // logs 'Alice'
console.log(alice.occupation); // logs 'Teacher'
英文:

Inspired by @gog's answer, I would like to share a modified version that works better for me. This solution

  1. avoids defining a temporary class that is ultimately not used. (e.g. the empty Apple class in gog's answer),
  2. is a clean way to initialize variables in 'superclasses' (with gog's original code, I wasn't able to find a clean way to define and inherit variables in the 'superclasses', which makes it 'unsafe' if I want to define functions in the 'childclass' that uses these variables.

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

    function augment(ins, ...mixins) {
for (let c of mixins)
for (let p of Object.getOwnPropertyNames(c.prototype))
if (p !== &#39;constructor&#39;)
ins

= c.prototype

} class Alice { initAlice() { this.name = &#39;Alice&#39;; } } class Teacher { initTeacher() { this.occupation = &#39;Teacher&#39;; } } class RealAlice { constructor() { augment(this,Alice,Teacher); this.initAlice(); this.initTeacher(); } } const alice = new RealAlice(30); console.log(alice.name); // logs &#39;Alice&#39; console.log(alice.occupation); // logs &#39;Teacher&#39;

<!-- end snippet -->

答案5

得分: 0

以下是您要翻译的内容:

"从我在问题的上面评论中...

> "这取决于一个人如何在术语上实现所有不同的类,取决于一个人如何希望将所有附加功能应用于它们,这又取决于一个人如何希望授予属性的可见性/保护/访问权。"

下面提供的示例完全涵盖了问题的要求... 主要基于两个基于函数的混入实现,每个都通过基于_共享私有状态的方法_ 来_针对特定特性/行为_,... 而提供的EarthAppleOrangeBanana的独立类实现反映了问题的设计方法相对不寻常,但也根据问题的要求应用了必要的混入。"

如果您需要进一步的翻译或有其他要求,请告诉我。

英文:

From my above comment on the OP's question ...

> "It depends on how one does implement all the distinct classes in terms of how one e.g. wants them all the additional functionality get applied to, which again depends on how one wants to grant property visibility / protection / access."

The next provided example covers the OP's specifications entirely ... mainly based on two function-based mixin implementations, each targeting a specific trait/behavior via an approach based on shared private state, ... whereas the provided solitary class implementations of Earth, Apple, Orange and Banana reflect the OP's rather unusual design approach, but also do apply each the necessary mixin/s according to the OP's specifications.

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

// - function-based mixin implementations
//   each targeting a specific trait/behavior
//   via an approach based on shared private state.
function withFreshnessIndication(sharedPrivateState) {
this.isFresh = () =&gt; sharedPrivateState.isFresh;
}
function asSphericalObject(sharedPrivateState) {
Object.defineProperty(this, &#39;radius&#39;, {
get: () =&gt; sharedPrivateState.radius,
});
}
// - Earth applies the trait of an spherical object
//   which is the only thing it will have in common
//   with Apple and Orange.
class Earth {
constructor() {
// radius in meter.
const state = { radius: 6_371_000 };
// code-reuse via mixin application.
asSphericalObject.call(this, state);
}
// - prototypal access of the locally encapsulated
//   `state` object is not anymore possible.
}
// - Apple applies both traits, the one of an
//   spherical object and the one of indicating
//   its freshness which it does have in common
//   with Orange.
class Apple {
#state;
constructor({ isFresh = true, radius = 0.05 } = {}) {
// radius in meter.
this.#state = { isFresh: Boolean(isFresh), radius };
// code-reuse via mixin application.
withFreshnessIndication.call(this, this.#state);
asSphericalObject.call(this, this.#state);
}
// - prototypal access of the privatly declared
//   `#state` object is still possible.
}
// - A lot of code duplication (no code-reuse)
//   due to the OP&#39;s base type/object design.
class Orange {
#state;
constructor({ isFresh = true, radius = 0.08 } = {}) {
// radius in meter.
this.#state = { isFresh: Boolean(isFresh), radius };
// code-reuse via mixin application.
withFreshnessIndication.call(this, this.#state);
asSphericalObject.call(this, this.#state);
}
}
// - Banana comes without the trait of an spherical object.
//   (again some code-duplication due to the OP&#39;s design.)
class Banana {
#state;
constructor({ isFresh = true } = {}) {
this.#state = { isFresh: Boolean(isFresh) };
// code-reuse via mixin application.
withFreshnessIndication.call(this, this.#state);
}
}
const earth = new Earth;
const apple = new Apple({ radius: .04 });
const orange = new Orange;
const banana = new Banana({ isFresh: false,  radius: 42 });
console.log(&#39;earth ...&#39;, {
isFresh: earth.isFresh?.(),
radius: earth.radius,
});
console.log(&#39;apple ...&#39;, {
isFresh: apple.isFresh(),
radius: apple.radius,
});
console.log(&#39;orange ...&#39;, {
isFresh: orange.isFresh(),
radius: orange.radius,
});
console.log(&#39;banana ...&#39;, {
isFresh: banana.isFresh(),
radius: banana.radius,
});

<!-- language: lang-css -->

.as-console-wrapper { min-height: 100%!important; top: 0; }

<!-- end snippet -->

huangapple
  • 本文由 发表于 2023年5月11日 15:33:48
  • 转载请务必保留本文链接:https://go.coder-hub.com/76225128.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定