ক্লাসগুলি একাধিক উত্তরাধিকারের সাথে সংজ্ঞায়িত করার অনুমতি দেওয়ার জন্য আমার যথেষ্ট ফাংশন রয়েছে। এটি নিম্নলিখিত মত কোডের জন্য অনুমতি দেয়। সামগ্রিকভাবে আপনি জাভাস্ক্রিপ্টে স্থানীয় ক্লাসিং কৌশলগুলি থেকে সম্পূর্ণ প্রস্থান লক্ষ্য করবেন (উদাহরণস্বরূপ আপনি class
কীওয়ার্ডটি কখনই দেখতে পাবেন না ):
let human = new Running({ name: 'human', numLegs: 2 });
human.run();
let airplane = new Flying({ name: 'airplane', numWings: 2 });
airplane.fly();
let dragon = new RunningFlying({ name: 'dragon', numLegs: 4, numWings: 6 });
dragon.takeFlight();
এভাবে আউটপুট উত্পাদন করতে:
human runs with 2 legs.
airplane flies away with 2 wings!
dragon runs with 4 legs.
dragon flies away with 6 wings!
এখানে শ্রেণীর সংজ্ঞাগুলি দেখতে কেমন:
let Named = makeClass('Named', {}, () => ({
init: function({ name }) {
this.name = name;
}
}));
let Running = makeClass('Running', { Named }, protos => ({
init: function({ name, numLegs }) {
protos.Named.init.call(this, { name });
this.numLegs = numLegs;
},
run: function() {
console.log(`${this.name} runs with ${this.numLegs} legs.`);
}
}));
let Flying = makeClass('Flying', { Named }, protos => ({
init: function({ name, numWings }) {
protos.Named.init.call(this, { name });
this.numWings = numWings;
},
fly: function( ){
console.log(`${this.name} flies away with ${this.numWings} wings!`);
}
}));
let RunningFlying = makeClass('RunningFlying', { Running, Flying }, protos => ({
init: function({ name, numLegs, numWings }) {
protos.Running.init.call(this, { name, numLegs });
protos.Flying.init.call(this, { name, numWings });
},
takeFlight: function() {
this.run();
this.fly();
}
}));
আমরা দেখতে পাচ্ছি যে makeClass
ফাংশনটি ব্যবহার করে প্রতিটি শ্রেণির সংজ্ঞা Object
পিতামাতা-শ্রেণিতে ম্যাপযুক্ত পিতৃত-শ্রেণীর নামের একটি গ্রহণ করে । এটি এমন কোনও ফাংশনও গ্রহণ করে যা Object
শ্রেণীর সংজ্ঞায়িত হওয়ার জন্য একটি বৈশিষ্ট্যযুক্ত বৈশিষ্ট্য দেয়। এই ফাংশনটির একটি প্যারামিটার রয়েছেprotos
রয়েছে, যার মধ্যে প্যারেন্ট-ক্লাসগুলির দ্বারা নির্ধারিত কোনও সম্পত্তি অ্যাক্সেস করার জন্য পর্যাপ্ত তথ্য রয়েছে।
প্রয়োজনীয় চূড়ান্ত টুকরাটি হ'ল makeClass
ফাংশনটি, যা বেশ কিছুটা কাজ করে। বাকি কোড সহ এটি এখানে। আমি makeClass
বেশ ভারী মন্তব্য করেছি :
let makeClass = (name, parents={}, propertiesFn=()=>({})) => {
// The constructor just curries to a Function named "init"
let Class = function(...args) { this.init(...args); };
// This allows instances to be named properly in the terminal
Object.defineProperty(Class, 'name', { value: name });
// Tracking parents of `Class` allows for inheritance queries later
Class.parents = parents;
// Initialize prototype
Class.prototype = Object.create(null);
// Collect all parent-class prototypes. `Object.getOwnPropertyNames`
// will get us the best results. Finally, we'll be able to reference
// a property like "usefulMethod" of Class "ParentClass3" with:
// `parProtos.ParentClass3.usefulMethod`
let parProtos = {};
for (let parName in parents) {
let proto = parents[parName].prototype;
parProtos[parName] = {};
for (let k of Object.getOwnPropertyNames(proto)) {
parProtos[parName][k] = proto[k];
}
}
// Resolve `properties` as the result of calling `propertiesFn`. Pass
// `parProtos`, so a child-class can access parent-class methods, and
// pass `Class` so methods of the child-class have a reference to it
let properties = propertiesFn(parProtos, Class);
properties.constructor = Class; // Ensure "constructor" prop exists
// If two parent-classes define a property under the same name, we
// have a "collision". In cases of collisions, the child-class *must*
// define a method (and within that method it can decide how to call
// the parent-class methods of the same name). For every named
// property of every parent-class, we'll track a `Set` containing all
// the methods that fall under that name. Any `Set` of size greater
// than one indicates a collision.
let propsByName = {}; // Will map property names to `Set`s
for (let parName in parProtos) {
for (let propName in parProtos[parName]) {
// Now track the property `parProtos[parName][propName]` under the
// label of `propName`
if (!propsByName.hasOwnProperty(propName))
propsByName[propName] = new Set();
propsByName[propName].add(parProtos[parName][propName]);
}
}
// For all methods defined by the child-class, create or replace the
// entry in `propsByName` with a Set containing a single item; the
// child-class' property at that property name (this also guarantees
// there is no collision at this property name). Note property names
// prefixed with "$" will be considered class properties (and the "$"
// will be removed).
for (let propName in properties) {
if (propName[0] === '$') {
// The "$" indicates a class property; attach to `Class`:
Class[propName.slice(1)] = properties[propName];
} else {
// No "$" indicates an instance property; attach to `propsByName`:
propsByName[propName] = new Set([ properties[propName] ]);
}
}
// Ensure that "init" is defined by a parent-class or by the child:
if (!propsByName.hasOwnProperty('init'))
throw Error(`Class "${name}" is missing an "init" method`);
// For each property name in `propsByName`, ensure that there is no
// collision at that property name, and if there isn't, attach it to
// the prototype! `Object.defineProperty` can ensure that prototype
// properties won't appear during iteration with `in` keyword:
for (let propName in propsByName) {
let propsAtName = propsByName[propName];
if (propsAtName.size > 1)
throw new Error(`Class "${name}" has conflict at "${propName}"`);
Object.defineProperty(Class.prototype, propName, {
enumerable: false,
writable: true,
value: propsAtName.values().next().value // Get 1st item in Set
});
}
return Class;
};
let Named = makeClass('Named', {}, () => ({
init: function({ name }) {
this.name = name;
}
}));
let Running = makeClass('Running', { Named }, protos => ({
init: function({ name, numLegs }) {
protos.Named.init.call(this, { name });
this.numLegs = numLegs;
},
run: function() {
console.log(`${this.name} runs with ${this.numLegs} legs.`);
}
}));
let Flying = makeClass('Flying', { Named }, protos => ({
init: function({ name, numWings }) {
protos.Named.init.call(this, { name });
this.numWings = numWings;
},
fly: function( ){
console.log(`${this.name} flies away with ${this.numWings} wings!`);
}
}));
let RunningFlying = makeClass('RunningFlying', { Running, Flying }, protos => ({
init: function({ name, numLegs, numWings }) {
protos.Running.init.call(this, { name, numLegs });
protos.Flying.init.call(this, { name, numWings });
},
takeFlight: function() {
this.run();
this.fly();
}
}));
let human = new Running({ name: 'human', numLegs: 2 });
human.run();
let airplane = new Flying({ name: 'airplane', numWings: 2 });
airplane.fly();
let dragon = new RunningFlying({ name: 'dragon', numLegs: 4, numWings: 6 });
dragon.takeFlight();
makeClass
ফাংশন এছাড়াও বর্গ বৈশিষ্ট্য সমর্থন করে; এগুলি $
প্রতীক সহ সম্পত্তি নাম উপসর্গ দ্বারা সংজ্ঞায়িত করা হয় (নোট করুন যে চূড়ান্ত সম্পত্তি নাম যে ফলাফল $
মুছে ফেলা হবে)। এটি মাথায় রেখে আমরা একটি বিশেষায়িত Dragon
শ্রেণি লিখতে পারি যা ড্রাগনের "ধরণের" মডেল করে, যেখানে উপলব্ধ ড্রাগনের ধরণের তালিকা ক্লাসে খোদাই করা থাকে, উদাহরণগুলির বিপরীতে:
let Dragon = makeClass('Dragon', { RunningFlying }, protos => ({
$types: {
wyvern: 'wyvern',
drake: 'drake',
hydra: 'hydra'
},
init: function({ name, numLegs, numWings, type }) {
protos.RunningFlying.init.call(this, { name, numLegs, numWings });
this.type = type;
},
description: function() {
return `A ${this.type}-type dragon with ${this.numLegs} legs and ${this.numWings} wings`;
}
}));
let dragon1 = new Dragon({ name: 'dragon1', numLegs: 2, numWings: 4, type: Dragon.types.drake });
let dragon2 = new Dragon({ name: 'dragon2', numLegs: 4, numWings: 2, type: Dragon.types.hydra });
একাধিক উত্তরাধিকারের চ্যালেঞ্জসমূহ
কোডটি makeClass
নিবিড়ভাবে অনুসরণ করেছেন এমন যে কেউ উপরের কোডটি চলাকালীন নিঃশব্দে ঘটে যাওয়া একটি উল্লেখযোগ্য অনাকাঙ্ক্ষিত ঘটনাটি নোট করবেন: একটি তাত্ক্ষণিকভাবে RunningFlying
ফলাফলটি Named
কনস্ট্রাক্টরের কাছে দু'বার ডাকবে !
এটি কারণ উত্তরাধিকারের গ্রাফটি দেখতে এই রকম:
(^^ More Specialized ^^)
RunningFlying
/ \
/ \
Running Flying
\ /
\ /
Named
(vv More Abstract vv)
যখন একটি উপ-শ্রেণীর উত্তরাধিকার গ্রাফে একই পিতাম-শ্রেণীর একাধিক পাথ থাকে ইনস্ট্যান্টেশনগুলি সেই অভিভাবক-শ্রেণীর' নির্মাতাকে একাধিকবার ডাকে।
এটির বিরুদ্ধে লড়াই করা তুচ্ছ বিষয় নয়। আসুন সরল শ্রেণীবদ্ধ সহ কয়েকটি উদাহরণ দেখি। আমরা শ্রেণি A
, সর্বাধিক বিমূর্ত অভিভাবক-শ্রেণি, শ্রেণি B
এবং C
উভয়ই যে উত্তরাধিকার সূত্রে প্রাপ্ত A
এবং ক্লাস BC
যা উত্তরাধিকার সূত্রে প্রাপ্ত হয়েছে B
এবং C
(এবং তাই ধারণাগতভাবে "ডাবল-উত্তরাধিকারী" থেকে বিবেচনা করব A
):
let A = makeClass('A', {}, () => ({
init: function() {
console.log('Construct A');
}
}));
let B = makeClass('B', { A }, protos => ({
init: function() {
protos.A.init.call(this);
console.log('Construct B');
}
}));
let C = makeClass('C', { A }, protos => ({
init: function() {
protos.A.init.call(this);
console.log('Construct C');
}
}));
let BC = makeClass('BC', { B, C }, protos => ({
init: function() {
// Overall "Construct A" is logged twice:
protos.B.init.call(this); // -> console.log('Construct A'); console.log('Construct B');
protos.C.init.call(this); // -> console.log('Construct A'); console.log('Construct C');
console.log('Construct BC');
}
}));
আমরা যদি BC
দ্বৈত-আহ্বান থেকে রোধ করতে চাই তবে আমাদের A.prototype.init
উত্তরাধিকার সূত্রে প্রাপ্ত কন্ট্রাক্টরকে সরাসরি কল করার স্টাইলটি ত্যাগ করতে হবে। ডুপ্লিকেট কলগুলি ঘটছে কিনা এবং তা হওয়ার আগে শর্ট সার্কিটের পরীক্ষা করতে আমাদের কিছু স্তরের ইন্ডিয়ারেশন প্রয়োজন হবে।
আমরা বৈশিষ্ট্য ফাংশনে সরবরাহিত প্যারামিটারগুলি পরিবর্তনের বিষয়ে বিবেচনা করতে পারি: বরাবর protos
, Object
উত্তরাধিকারসূত্রে প্রাপ্ত বৈশিষ্ট্যগুলি বর্ণনা করে এমন একটি কাঁচা ডেটা, আমরা উদাহরণস্বরূপ পদ্ধতিটি কল করার জন্য একটি ইউটিলিটি ফাংশনও অন্তর্ভুক্ত করতে পারি যে প্যারেন্ট পদ্ধতিগুলিও বলা হয়, তবে সদৃশ কলগুলি সনাক্ত করা হয় এবং প্রতিরোধ। আসুন আমরা এর জন্য প্যারামিটারগুলি কোথায় স্থাপন করি তা একবার দেখে নেওয়া যাক propertiesFn
Function
:
let makeClass = (name, parents, propertiesFn) => {
/* ... a bunch of makeClass logic ... */
// Allows referencing inherited functions; e.g. `parProtos.ParentClass3.usefulMethod`
let parProtos = {};
/* ... collect all parent methods in `parProtos` ... */
// Utility functions for calling inherited methods:
let util = {};
util.invokeNoDuplicates = (instance, fnName, args, dups=new Set()) => {
// Invoke every parent method of name `fnName` first...
for (let parName of parProtos) {
if (parProtos[parName].hasOwnProperty(fnName)) {
// Our parent named `parName` defines the function named `fnName`
let fn = parProtos[parName][fnName];
// Check if this function has already been encountered.
// This solves our duplicate-invocation problem!!
if (dups.has(fn)) continue;
dups.add(fn);
// This is the first time this Function has been encountered.
// Call it on `instance`, with the desired args. Make sure we
// include `dups`, so that if the parent method invokes further
// inherited methods we don't lose track of what functions have
// have already been called.
fn.call(instance, ...args, dups);
}
}
};
// Now we can call `propertiesFn` with an additional `util` param:
// Resolve `properties` as the result of calling `propertiesFn`:
let properties = propertiesFn(parProtos, util, Class);
/* ... a bunch more makeClass logic ... */
};
উপরের পরিবর্তনের পুরো উদ্দেশ্যটি makeClass
হ'ল যাতে propertiesFn
আমরা প্রার্থনা করি তখন আমাদের কাছে অতিরিক্ত যুক্তি সরবরাহ করা হয় makeClass
। আমাদের এও সচেতন হওয়া উচিত যে যে কোনও শ্রেণিতে সংজ্ঞায়িত প্রতিটি ক্রিয়াকলাপ এখন নাম অনুসারে তার সমস্তগুলির পরে একটি প্যারামিটার পেতে পারে dup
, যা Set
উত্তরাধিকার সূত্রে প্রাপ্ত পদ্ধতিটি কল করার ফলে ইতিমধ্যে আহ্বান করা সমস্ত ফাংশন ধারণ করে:
let A = makeClass('A', {}, () => ({
init: function() {
console.log('Construct A');
}
}));
let B = makeClass('B', { A }, (protos, util) => ({
init: function(dups) {
util.invokeNoDuplicates(this, 'init', [ /* no args */ ], dups);
console.log('Construct B');
}
}));
let C = makeClass('C', { A }, (protos, util) => ({
init: function(dups) {
util.invokeNoDuplicates(this, 'init', [ /* no args */ ], dups);
console.log('Construct C');
}
}));
let BC = makeClass('BC', { B, C }, (protos, util) => ({
init: function(dups) {
util.invokeNoDuplicates(this, 'init', [ /* no args */ ], dups);
console.log('Construct BC');
}
}));
এই নতুন শৈলীটি আসলে "Construct A"
একবারে একবার লগ হয় তা নিশ্চিত করতে সফল হয় যখন কোনও উদাহরণ BC
শুরু হয়। তবে তিনটি ডাউনসাইড রয়েছে, তৃতীয়টি অত্যন্ত সমালোচনামূলক :
- এই কোডটি কম পঠনযোগ্য এবং রক্ষণাবেক্ষণযোগ্য হয়ে উঠেছে।
util.invokeNoDuplicates
ফাংশনটির পিছনে অনেক জটিলতা লুকিয়ে থাকে এবং এই স্টাইলটি কীভাবে বহু-আহবানকে এড়িয়ে চলে সে সম্পর্কে চিন্তাভাবনা অ-স্বজ্ঞাত এবং মাথা ব্যথাকে প্ররোচিত করে। আমাদের কাছে সেই অদ্ভুত dups
প্যারামিটারও রয়েছে, যা ক্লাসের প্রতিটি ফাংশনে সত্যই সংজ্ঞায়িত করা দরকার । সেকি।
- এই কোডটি ধীরে ধীরে - একাধিক উত্তরাধিকারের সাথে কাঙ্ক্ষিত ফলাফল অর্জনের জন্য আরও কিছুটা ইন্ডিরিয়েশন এবং গণনার প্রয়োজন। দুর্ভাগ্যবশত, এই মামলাটি হতে পারে কোনো আমাদের মাল্টিপল-আবাহন সমস্যার সমাধান।
- সর্বাধিক উল্লেখযোগ্যভাবে, উত্তরাধিকারের উপর নির্ভরশীল ফাংশনগুলির কাঠামোটি খুব কঠোর হয়ে উঠেছে । যদি একটি উপ-শ্রেণি
NiftyClass
কোনও ফাংশনকে ওভাররাইড করে niftyFunction
এবং util.invokeNoDuplicates(this, 'niftyFunction', ...)
সদৃশ-অনুরোধ ছাড়াই এটি চালানোর জন্য ব্যবহার করে, তবে প্রতিটি পিতামাতা শ্রেণীর NiftyClass.prototype.niftyFunction
নাম চিহ্নিত ফাংশনটিকে niftyFunction
এটি সংজ্ঞায়িত করবে, those শ্রেণি থেকে কোনও প্রত্যাবর্তন মান উপেক্ষা করবে এবং অবশেষে এর বিশেষায়িত যুক্তি সম্পাদন করবে NiftyClass.prototype.niftyFunction
। এটিই সম্ভব সম্ভাব্য কাঠামো । যদি NiftyClass
উত্তরাধিকারসূত্রে হয় CoolClass
এবং GoodClass
এবং এই উভয় পিতাম-শ্রেণি niftyFunction
তাদের নিজস্ব সংজ্ঞা সরবরাহ করে NiftyClass.prototype.niftyFunction
তবে কখনই (একাধিক প্রার্থনার ঝুঁকি ছাড়াই) সক্ষম হতে পারবে না:
- উ:
NiftyClass
প্রথমে বিশেষায়িত যুক্তি চালান , তারপরে পিতা-মাতার শ্রেণীর বিশেষ যুক্তি যুক্ত করুন
- বি। সমস্ত বিশেষায়িত প্যারেন্ট যুক্তি শেষ হওয়ার পরে
NiftyClass
অন্য যে কোনও সময়ে বিশেষায়িত যুক্তি চালান
- সি তার পিতামাতার বিশেষ যুক্তির ফিরে আসার মানের উপর নির্ভর করে শর্তাধীন আচরণ করুন
- ডি চলুন একটি নির্দিষ্ট পিতা বা মাতা চলমান বিশেষ হচ্ছে
niftyFunction
পুরাপুরি
অবশ্যই, আমরা নীচে বিশেষ ফাংশনগুলি সংজ্ঞায়িত করে উপরে প্রতিটি বর্ণিত সমস্যা সমাধান করতে পারি util
:
- উ: সংজ্ঞায়িত
util.invokeNoDuplicatesSubClassLogicFirst(instance, fnName, ...)
- বি। সংজ্ঞায়িত করুন
util.invokeNoDuplicatesSubClassAfterParent(parentName, instance, fnName, ...)
( parentName
পিতা-মাতার নাম কোথায় যেখানে বিশেষায়িত যুক্তি তাত্ক্ষণিকভাবে শিশু-শ্রেণীর বিশেষায়িত যুক্তি অনুসরণ করবে)
- সি। সংজ্ঞায়িত করুন
util.invokeNoDuplicatesCanShortCircuitOnParent(parentName, testFn, instance, fnName, ...)
(এই ক্ষেত্রে testFn
অভিভাবকের নামকরণের জন্য বিশেষ যুক্তির ফলাফল প্রাপ্ত parentName
হবে true/false
এবং সংক্ষিপ্ত সার্কিটটি ঘটবে কিনা তা নির্দেশ করে একটি মূল্য ফেরত দেবে )
- ডি সংজ্ঞায়িত
util.invokeNoDuplicatesBlackListedParents(blackList, instance, fnName, ...)
(এই ক্ষেত্রে blackList
একটি হবে Array
পিতা বা মাতা নাম যার বিশেষ যুক্তিবিজ্ঞান পুরাপুরি এড়ানো করা উচিত)
এই সমাধানগুলি সমস্ত উপলভ্য, তবে এটি মোট বিপর্যয় ! উত্তরাধিকার সূত্রে ফাংশন কল গ্রহণ করতে পারে এমন প্রতিটি অনন্য কাঠামোর জন্য আমাদের অধীনে একটি বিশেষ পদ্ধতি প্রয়োজন util
। কি পরম দুর্যোগ।
এটি মাথায় রেখে আমরা ভাল একাধিক উত্তরাধিকার বাস্তবায়নের চ্যালেঞ্জগুলি দেখতে শুরু করতে পারি। makeClass
আমি এই উত্তরের প্রদত্ত সম্পূর্ণ বাস্তবায়ন এমনকি একাধিক-আহবান সমস্যা, বা একাধিক উত্তরাধিকার সম্পর্কিত উত্পন্ন অন্যান্য অনেক সমস্যাও বিবেচনা করে না।
এই উত্তরটি খুব দীর্ঘ হচ্ছে। আমি আশা করি makeClass
আমার অন্তর্ভুক্ত করা বাস্তবায়ন এখনও নিখুঁত না হলেও কার্যকর হবে। আমি আরও আশা করি যে এই বিষয়ে আগ্রহী কেউ আরও পড়ার সাথে সাথে মনে রাখার জন্য আরও প্রসঙ্গ অর্জন করেছে!