javascript 如何在没有继承的情况下实现跨各种不同类应用的泛型功能?

camsedfj  于 2023-05-21  发布在  Java
关注(0)|答案(5)|浏览(118)

我需要定义一个可以被多个类使用的函数,但据我所知,从超类继承对我来说不起作用。本质上,我想要实现的是为每个类扩展多个接口的能力。
例如,如果我定义了AppleOrangeBanana类,我希望它们都有一个相同的isFresh()函数。我还喜欢让AppleOrangeEarth拥有一个getRadius()方法。这有点类似于Apple interface Fruit, SphericalObject {...}我也希望能够覆盖的功能,如果我想。然而,继承对我不起作用,因为我想从多个超类继承。
实现这一目标的最佳途径是什么?
我知道这个similar post,我理解JavaScript是动态类型的,没有接口,建议的Duck Type似乎不能解决我的问题。我并不关心检查接口中的方法是否存在于子类中。

kcugc4gi

kcugc4gi1#

看起来你在找“混合”它们不是内置在JavaScript中的,但在用户环境中很容易实现,例如:

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())
k75qkfdt

k75qkfdt2#

您只需要一个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());
ds97pgxw

ds97pgxw3#

关于被广泛误解的风险:受Douglas Crockford的启发,我停止使用类或原型(好吧,我在ES中从未使用过的类,never had any use for it)。
创建factory functions。这是一个示范性的水果工厂。
为了实现这个想法,我创建了一个小的Stackblitz project,使用了一种更通用的方法。

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 = {};
  // all fruits 'inherit' 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'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]) => 
      value || key in fruitDefaults ? fruit[key] = value : false 
    );
    return fruit;
  }
}
.as-console-wrapper {
    max-height: 100% !important;
}
u1ehiz5o

u1ehiz5o4#

受@gog的answer的启发,我想分享一个更适合我的修改版本。这个解决方案
1.避免了定义最终不使用的临时类。(例如gog的答案中的空Apple类)
1.是一种干净的方法来初始化“超类”中的变量(使用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'
2izufjch

2izufjch5#

从我上面对OP的问题的评论中...

  • “这取决于一个人如何实现所有不同的类,例如希望它们应用所有附加功能,这同样取决于希望如何授予属性可见性/保护/访问权限。"*

下一个提供的示例完全涵盖了OP的规范...主要基于两个***基于函数的mixin*实现,每个通过基于共享私有状态的方法针对特定的特性/行为,...而提供的EarthAppleOrangeBanana的单独类实现反映了OP相当不寻常的设计方法,但也确实根据OP的规范应用了每个必要的mixin。

// - function-based mixin implementations
//   each targeting a specific trait/behavior
//   via an approach based on shared private state.

function withFreshnessIndication(sharedPrivateState) {
  this.isFresh = () => sharedPrivateState.isFresh;
}
function asSphericalObject(sharedPrivateState) {
  Object.defineProperty(this, 'radius', {
    get: () => 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'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'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('earth ...', {
  isFresh: earth.isFresh?.(),
  radius: earth.radius,
});
console.log('apple ...', {
  isFresh: apple.isFresh(),
  radius: apple.radius,
});
console.log('orange ...', {
  isFresh: orange.isFresh(),
  radius: orange.radius,
});
console.log('banana ...', {
  isFresh: banana.isFresh(),
  radius: banana.radius,
});
.as-console-wrapper { min-height: 100%!important; top: 0; }

相关问题