العربية

اكتشف الأصناف المجردة في TypeScript ومزاياها والأنماط المتقدمة للتنفيذ الجزئي، مما يعزز إعادة استخدام الكود والمرونة في المشاريع المعقدة. يتضمن أمثلة عملية وأفضل الممارسات.

الأصناف المجردة في TypeScript: إتقان أنماط التنفيذ الجزئي

الأصناف المجردة هي مفهوم أساسي في البرمجة كائنية التوجه (OOP)، حيث توفر مخططًا للأصناف الأخرى. في TypeScript، تقدم الأصناف المجردة آلية قوية لتعريف الوظائف المشتركة مع فرض متطلبات تنفيذ محددة على الأصناف المشتقة. تتعمق هذه المقالة في تعقيدات الأصناف المجردة في TypeScript، مع التركيز على الأنماط العملية للتنفيذ الجزئي، وكيف يمكنها تعزيز إعادة استخدام الكود وقابلية الصيانة والمرونة في مشاريعك بشكل كبير.

ما هي الأصناف المجردة؟

الصنف المجرد في TypeScript هو صنف لا يمكن إنشاء نسخة منه مباشرة. إنه بمثابة صنف أساسي للأصناف الأخرى، حيث يحدد مجموعة من الخصائص والدوال التي يجب على الأصناف المشتقة تنفيذها (أو تجاوزها). يتم الإعلان عن الأصناف المجردة باستخدام الكلمة المفتاحية abstract.

الخصائص الرئيسية:

لماذا نستخدم الأصناف المجردة؟

تقدم الأصناف المجردة العديد من المزايا في تطوير البرمجيات:

مثال أساسي على صنف مجرد

لنبدأ بمثال بسيط لتوضيح البنية الأساسية لصنف مجرد في TypeScript:


abstract class Animal {
 abstract makeSound(): string;

 move(): void {
 console.log("Moving...");
 }
}

class Dog extends Animal {
 makeSound(): string {
 return "Woof!";
 }
}

class Cat extends Animal {
 makeSound(): string {
 return "Meow!";
 }
}

//const animal = new Animal(); // Error: Cannot create an instance of an abstract class.

const dog = new Dog();
console.log(dog.makeSound()); // Output: Woof!
dog.move(); // Output: Moving...

const cat = new Cat();
console.log(cat.makeSound()); // Output: Meow!
cat.move(); // Output: Moving...

في هذا المثال، Animal هو صنف مجرد يحتوي على دالة مجردة makeSound() ودالة ملموسة move(). يقوم الصنفان Dog و Cat بتوسيع Animal ويوفران تنفيذات ملموسة للدالة makeSound(). لاحظ أن محاولة إنشاء نسخة مباشرة من `Animal` تؤدي إلى خطأ.

أنماط التنفيذ الجزئي

أحد الجوانب القوية للأصناف المجردة هو القدرة على تحديد تنفيذات جزئية. يتيح لك هذا توفير تنفيذ افتراضي لبعض الدوال بينما يتطلب من الأصناف المشتقة تنفيذ دوال أخرى. هذا يوازن بين إعادة استخدام الكود والمرونة.

1. الدوال المجردة مع تنفيذات افتراضية في الأصناف المشتقة

في هذا النمط، يعلن الصنف المجرد عن دالة مجردة *يجب* تنفيذها بواسطة الأصناف المشتقة، ولكنه لا يقدم أي تنفيذ أساسي. هذا يجبر الأصناف المشتقة على توفير منطقها الخاص.


abstract class DataProcessor {
 abstract fetchData(): Promise;
 abstract processData(data: any): any;
 abstract saveData(processedData: any): Promise;

 async run(): Promise {
 const data = await this.fetchData();
 const processedData = this.processData(data);
 await this.saveData(processedData);
 }
}

class APIProcessor extends DataProcessor {
 async fetchData(): Promise {
 // Implementation to fetch data from an API
 console.log("Fetching data from API...");
 return { data: "API Data" }; // Mock data
 }

 processData(data: any): any {
 // Implementation to process data specific to API data
 console.log("Processing API data...");
 return { processed: data.data + " - Processed" }; // Mock processed data
 }

 async saveData(processedData: any): Promise {
 // Implementation to save processed data to a database via API
 console.log("Saving processed API data...");
 console.log(processedData);
 }
}

const apiProcessor = new APIProcessor();
apiProcessor.run();

في هذا المثال، يحدد الصنف المجرد DataProcessor ثلاث دوال مجردة: fetchData()، processData()، و saveData(). يقوم الصنف APIProcessor بتوسيع DataProcessor ويوفر تنفيذات ملموسة لكل من هذه الدوال. تقوم الدالة run()، المحددة في الصنف المجرد، بتنسيق العملية بأكملها، مما يضمن تنفيذ كل خطوة بالترتيب الصحيح.

2. الدوال الملموسة ذات التبعيات المجردة

يتضمن هذا النمط دوالًا ملموسة في الصنف المجرد تعتمد على دوال مجردة لأداء مهام محددة. يتيح لك هذا تحديد خوارزمية مشتركة مع تفويض تفاصيل التنفيذ إلى الأصناف المشتقة.


abstract class PaymentProcessor {
 abstract validatePaymentDetails(paymentDetails: any): boolean;
 abstract chargePayment(paymentDetails: any): Promise;
 abstract sendConfirmationEmail(paymentDetails: any): Promise;

 async processPayment(paymentDetails: any): Promise {
 if (!this.validatePaymentDetails(paymentDetails)) {
 console.error("Invalid payment details.");
 return false;
 }

 const chargeSuccessful = await this.chargePayment(paymentDetails);
 if (!chargeSuccessful) {
 console.error("Payment failed.");
 return false;
 }

 await this.sendConfirmationEmail(paymentDetails);
 console.log("Payment processed successfully.");
 return true;
 }
}

class CreditCardPaymentProcessor extends PaymentProcessor {
 validatePaymentDetails(paymentDetails: any): boolean {
 // Validate credit card details
 console.log("Validating credit card details...");
 return true; // Mock validation
 }

 async chargePayment(paymentDetails: any): Promise {
 // Charge credit card
 console.log("Charging credit card...");
 return true; // Mock charge
 }

 async sendConfirmationEmail(paymentDetails: any): Promise {
 // Send confirmation email for credit card payment
 console.log("Sending confirmation email for credit card payment...");
 }
}

const creditCardProcessor = new CreditCardPaymentProcessor();
creditCardProcessor.processPayment({ cardNumber: "1234-5678-9012-3456", expiryDate: "12/24", cvv: "123", amount: 100 });

في هذا المثال، يحدد الصنف المجرد PaymentProcessor دالة processPayment() التي تتعامل مع منطق معالجة الدفع بشكل عام. ومع ذلك، فإن الدوال validatePaymentDetails() و chargePayment() و sendConfirmationEmail() هي دوال مجردة، مما يتطلب من الأصناف المشتقة توفير تنفيذات محددة لكل طريقة دفع (مثل بطاقة الائتمان، PayPal، إلخ).

3. نمط الدالة القالب (Template Method Pattern)

نمط الدالة القالب هو نمط تصميم سلوكي يحدد الهيكل العام لخوارزمية في الصنف المجرد ولكنه يسمح للأصناف الفرعية بتجاوز خطوات محددة من الخوارزمية دون تغيير هيكلها. هذا النمط مفيد بشكل خاص عندما يكون لديك سلسلة من العمليات التي يجب تنفيذها بترتيب معين، ولكن قد يختلف تنفيذ بعض العمليات اعتمادًا على السياق.


abstract class ReportGenerator {
 abstract generateHeader(): string;
 abstract generateBody(): string;
 abstract generateFooter(): string;

 generateReport(): string {
 const header = this.generateHeader();
 const body = this.generateBody();
 const footer = this.generateFooter();

 return `${header}\n${body}\n${footer}`;
 }
}

class PDFReportGenerator extends ReportGenerator {
 generateHeader(): string {
 return "PDF Report Header";
 }

 generateBody(): string {
 return "PDF Report Body";
 }

 generateFooter(): string {
 return "PDF Report Footer";
 }
}

class CSVReportGenerator extends ReportGenerator {
 generateHeader(): string {
 return "CSV Report Header";
 }

 generateBody(): string {
 return "CSV Report Body";
 }

 generateFooter(): string {
 return "CSV Report Footer";
 }
}

const pdfReportGenerator = new PDFReportGenerator();
console.log(pdfReportGenerator.generateReport());

const csvReportGenerator = new CSVReportGenerator();
console.log(csvReportGenerator.generateReport());

هنا، يحدد `ReportGenerator` عملية إنشاء التقرير الإجمالية في `generateReport()`، بينما تُترك الخطوات الفردية (الرأس، الجسم، التذييل) للأصناف الفرعية الملموسة `PDFReportGenerator` و `CSVReportGenerator`.

4. الخصائص المجردة

يمكن للأصناف المجردة أيضًا تحديد خصائص مجردة، وهي خصائص يجب تنفيذها في الأصناف المشتقة. هذا مفيد لفرض وجود عناصر بيانات معينة في الأصناف المشتقة.


abstract class Configuration {
 abstract apiKey: string;
 abstract apiUrl: string;

 getFullApiUrl(): string {
 return `${this.apiUrl}/${this.apiKey}`;
 }
}

class ProductionConfiguration extends Configuration {
 apiKey: string = "prod_api_key";
 apiUrl: string = "https://api.example.com/prod";
}

class DevelopmentConfiguration extends Configuration {
 apiKey: string = "dev_api_key";
 apiUrl: string = "http://localhost:3000/dev";
}

const prodConfig = new ProductionConfiguration();
console.log(prodConfig.getFullApiUrl()); // Output: https://api.example.com/prod/prod_api_key

const devConfig = new DevelopmentConfiguration();
console.log(devConfig.getFullApiUrl()); // Output: http://localhost:3000/dev/dev_api_key

في هذا المثال، يحدد الصنف المجرد Configuration خاصيتين مجردتين: apiKey و apiUrl. يقوم الصنفان ProductionConfiguration و DevelopmentConfiguration بتوسيع Configuration ويوفران قيمًا ملموسة لهذه الخصائص.

اعتبارات متقدمة

المزج (Mixins) مع الأصناف المجردة

يسمح TypeScript بدمج الأصناف المجردة مع Mixins لإنشاء مكونات أكثر تعقيدًا وقابلية لإعادة الاستخدام. Mixins هي طريقة لبناء الأصناف عن طريق تجميع أجزاء صغيرة وقابلة لإعادة الاستخدام من الوظائف.


// Define a type for the constructor of a class
type Constructor = new (...args: any[]) => T;

// Define a mixin function
function Timestamped(Base: TBase) {
 return class extends Base {
 timestamp = new Date();
 };
}

// Another mixin function
function Logged(Base: TBase) {
 return class extends Base {
 log(message: string) {
 console.log(`${this.constructor.name}: ${message}`);
 }
 };
}

abstract class BaseEntity {
 abstract id: number;
}

// Apply the mixins to the BaseEntity abstract class
const TimestampedEntity = Timestamped(BaseEntity);
const LoggedEntity = Logged(TimestampedEntity);

class User extends LoggedEntity {
 id: number = 123;
 name: string = "John Doe";

 constructor() {
 super();
 this.log("User created");
 }
}

const user = new User();
console.log(user.id); // Output: 123
console.log(user.timestamp); // Output: Current timestamp
user.log("User updated"); // Output: User: User updated

يجمع هذا المثال بين Mixins Timestamped و Logged مع الصنف المجرد BaseEntity لإنشاء صنف User يرث وظائف الثلاثة جميعًا.

حقن التبعية (Dependency Injection)

يمكن استخدام الأصناف المجردة بشكل فعال مع حقن التبعية (DI) لفصل المكونات وتحسين قابلية الاختبار. يمكنك تعريف الأصناف المجردة كواجهات لتبعياتك ثم حقن التنفيذات الملموسة في أصنافك.


abstract class Logger {
 abstract log(message: string): void;
}

class ConsoleLogger extends Logger {
 log(message: string): void {
 console.log(`[Console]: ${message}`);
 }
}

class FileLogger extends Logger {
 log(message: string): void {
 // Implementation to log to a file
 console.log(`[File]: ${message}`);
 }
}

class AppService {
 private logger: Logger;

 constructor(logger: Logger) {
 this.logger = logger;
 }

 doSomething() {
 this.logger.log("Doing something...");
 }
}

// Inject the ConsoleLogger
const consoleLogger = new ConsoleLogger();
const appService1 = new AppService(consoleLogger);
appService1.doSomething();

// Inject the FileLogger
const fileLogger = new FileLogger();
const appService2 = new AppService(fileLogger);
appService2.doSomething();

في هذا المثال، يعتمد الصنف AppService على الصنف المجرد Logger. يتم حقن التنفيذات الملموسة (ConsoleLogger، FileLogger) في وقت التشغيل، مما يتيح لك التبديل بسهولة بين استراتيجيات التسجيل المختلفة.

أفضل الممارسات

الخاتمة

الأصناف المجردة في TypeScript هي أداة قوية لبناء تطبيقات قوية وقابلة للصيانة. من خلال فهم وتطبيق أنماط التنفيذ الجزئي، يمكنك الاستفادة من مزايا الأصناف المجردة لإنشاء كود مرن وقابل لإعادة الاستخدام ومنظم جيدًا. من تحديد الدوال المجردة ذات التنفيذات الافتراضية إلى استخدام الأصناف المجردة مع Mixins وحقن التبعية، فإن الإمكانيات واسعة. باتباع أفضل الممارسات والنظر بعناية في خيارات التصميم الخاصة بك، يمكنك استخدام الأصناف المجردة بفعالية لتعزيز جودة وقابلية توسع مشاريع TypeScript الخاصة بك.

سواء كنت تبني تطبيقًا كبيرًا على مستوى المؤسسات أو مكتبة أدوات صغيرة، فإن إتقان الأصناف المجردة في TypeScript سيحسن بلا شك مهاراتك في تطوير البرمجيات ويمكّنك من إنشاء حلول أكثر تطورًا وقابلية للصيانة.

الأصناف المجردة في TypeScript: إتقان أنماط التنفيذ الجزئي | MLOG