تطبيقات وأمثلة عملية للـ OOP
السلام عليكم ورحمة الله وبركاته
الفهرس
المقدمة
هذه المقالة تعد تطبيقات عملية على سلسلة الـ OOP
التي قمنا بشرح أساسياتها
لأنني لاحظت أنه برغم من أن الشخص قد يكون فهم مبادئ الـ OOP
بشكل جيد، إلا أنه قد يجد صعوبة في تطبيقها أو يفكر في كيف يبني مشروع بها
لذلك سنقوم بعمل بعض التطبيقات العملية للـ OOP
عن طريق عرض بعض الأمثلة والمشاكل وكيفية حلها بالـ OOP
سأبدأ بمثال بسيط ثم نتدرج فيه بالأفكار لتغطية معظم الأفكار والمباديء التي تعلمناها
المثال سيكون عمل برنامج بسيط يمثل مدونة، حيث يمكن للمستخدم إضافة مقالات جديدة وعرضها
وسنقوم بتطبيق الـ OOP
في هذا المثال
طبعًا عندما تفكر في مدونة فسيأتي في بالك مكونين أساسيين وهما المقالات و مألفوا المقالات
لذلك سنقوم بإنشاء كلاسين لكل منهما
إنشاء كلاس مؤلف المقالات
لنبدأ بالأبسط وهو الشخص الذي يكتب المقالات، سنقوم بإنشاء كلاس يمثل هذا المؤلف
عندما تفكر ما هي خصائص المؤلف ؟ ستجده مثله مثل أي شخص آخر، يحتوي على اسمه وبريده الإلكتروني ووصف قصير عنه
class Author {
public id: number;
public name: string;
public email: string;
public bio: string;
constructor(id: number, name: string, email: string, bio: string) {
this.id = id;
this.name = name;
this.email = email;
this.bio = bio;
}
}
هذا الكلاس يحتوي على الـ id
و name
و email
و bio
تمثل الخصائص التي تكون أي مؤلف
ويحتوي على constructor
لتمرير القيم عند إنشاء object
جديد من هذا الكلاس
يمكننا الآن إنشاء مؤلف جديد بكل سهولة
const author_1 = new Author(1, 'Ahmed', '[email protected]', 'Backend Developer at Quotem');
const author_2 = new Author(2, 'Ali', '[email protected]', 'Frontend Developer at Example');
حسنًا، الآن لدينا مؤلفين لكن قبل أن نكمل دعونا نتكلم عن بعض النقاط وكيف يمكننا تحسين الكود
أولًا من غير المنطقي أن نجعل الـ id
يتم تحديده من قبل المستخدم، علينا جعله يتم توليده تلقائيًا
لذا سنقوم بإضافة خاصية static
للكلاس تحتوي على id
يتم تحديده تلقائيًا
والـ static
كما تعرف هو جعل المتغيرات أو الدوال مرئية على مستوى الكلاس وليس الـ object
class Author {
private static lastId: number = 0;
private id: number;
public name: string;
public email: string;
public bio: string;
constructor(name: string, email: string, bio: string) {
this.name = name;
this.email = email;
this.bio = bio;
this.setGeneratedId();
}
private setGeneratedId() {
Author.lastId++;
this.id = Author.lastId;
}
public getId(): number {
return this.id;
}
}
هنا قمنا بإضافة متغير lastId
ولاحظ أنه private
لأنه لا يجب أن يتم الوصول إليه من خارج الكلاس
وأيضًا جعلناه static
لأنه يجب أن يكون مرئي على مستوى الكلاس وقمنا بإضافة دالة setGeneratedId
تقوم بتوليد id
جديد
الدالة هنا private
لأنها شيء يتم حسابه داخل الكلاس ولا يجب أن يتم الوصول إليه من خارج الكلاس
والدالة تقوم بتزويد الـ lastId
بـ 1
ثم تجعل id
تساوي lastId
الجديدة
ولاحظ أن الـ id
هنا أصبح private
لأنه لا يجب أن يتم تعديله من خارج الكلاس
ولكننا قمنا بإضافة دالة getId
تقوم بإرجاع الـ id
للمستخدم فقط
لذا قمنا بعمل getter
للـ id
ولم نقم بعمل setter
له بسبب غرض معين
وهذا من فوائد الـ OOP
ومبدأ الـ Encapsulation
في جمع البيانات والدوال المتعلقة بها في كلاس واحد وإخفاء البيانات عن المستخدم
والسماح بالوصول لبعض البيانات من خلال دوال معينة مثل ما سمحنا لك بالوصول لمتغير معين مثل الـ id
ولكن لا يسمح لك بتعديله
الآن يمكننا إنشاء مؤلف جديد بدون تحديد id
const author_1 = new Author('Ahmed', '[email protected]', 'Backend Developer at Quotem');
const author_2 = new Author('Ali', '[email protected]', 'Frontend Developer at Example');
console.log(author_1.getId()); // 1
console.log(author_2.getId()); // 2
لاحظ أن الـ id
تم توليده تلقائيًا مع كل مؤلف جديد أو object
جديد من الكلاس
وهذا أحد فوائد الـ static
وأحد استخداماته البسيطة
تخزين المؤلفين في قاعدة البيانات
أولًا نحتاج لشيء يمثل قاعدة البيانات الخاصة بالمؤلفين، لذا سنقوم بإنشاء كلاس جديد يمثل قاعدة البيانات
class AuthorDatabase {
private static authors: Author[] = [];
private constructor() {}
public static addAuthor(author: Author): void {
this.authors.push(author);
}
}
هنا قمنا بإنشاء كلاس AuthorDatabase
يحتوي على أراي من الـ Author
تمثل المؤلفين
وهناك دالة addAuthor
تقوم بإضافة مؤلف جديد إلى القاعدة
ستلاحظ أننا قمنا بجعل كل شيء static
سواء متغير الـ authors
أو الدالة addAuthor
لأن الكلاس AuthorDatabase
هو كما يوحي اسمه فهو قاعدة بيانات بالتالي لا داعي لإنشاء object
منه
بل يمكننا أن نتعامل معه مباشرة من خلال الكلاس نفسه
لذا طالما أننا لا نريد إنشاء object
من الكلاس بالتالي نجعل كل شيء static
وأيضًا نجعل الـ constructor
يكون private
لمنع إنشاء أي object
من الكلاس
كلاس بسيط واستخدامه أبسط
const author_1 = new Author('Ahmed', '[email protected]', 'Backend Developer at Quotem');
const author_2 = new Author('Ali', '[email protected]', 'Frontend Developer at Example');
AuthorDatabase.addAuthor(author_1);
AuthorDatabase.addAuthor(author_2);
لاحظ أننا هنا أنشأنا author_1
و author_2
ثم قمنا بإضافتهم إلى قاعدة البيانات AuthorDatabase
بشكل مباشر
لاحظ أننا لم نقم بإنشاء object
من الكلاس AuthorDatabase
بل قمنا بالوصول إلى الدالة addAuthor
مباشرة لأنها static
لكن سؤال ... ما فائدة الـ AuthorDatabase
؟ ولما نحتاج لكلاس يمثل قاعدة البيانات ؟ ونخزن فيه المؤلفين ؟
الفائدة هنا شيئين:
- لديك مكان واحد به بيانات كل لمؤلفين الذي تضيفهم
- يمكنك إضافة دوال أخرى تقوم بالبحث عن مؤلف معين أو تعديل بياناته أو حذفه
فمثًلا يمكنك إضافة دالة داخل AuthorDatabase
تقوم بإنشاء مؤلف جديد وإضافته إلى القاعدة
class AuthorDatabase {
private static authors: Author[] = [];
private constructor() {}
public static addAuthor(author: Author): void {
this.authors.push(author);
}
public static createAuthor(name: string, email: string, bio: string): Author {
const author = new Author(name, email, bio);
this.authors.push(author);
return author;
}
}
هنا قمنا بإضافة دالة createAuthor
تقوم بإنشاء مؤلف جديد وإضافته إلى قاعدة البيانات وتقوم بإرجاع المؤلف الجديد
بالتالي يمكنك الآن إنشاء مؤلف جديد وإضافته إلى قاعدة البيانات بشكل مباشر
AuthorDatabase.createAuthor('Ahmed', '[email protected]', 'Backend Developer at Quotem');
AuthorDatabase.createAuthor('Ali', '[email protected]', 'Frontend Developer at Example');
عمل بعض العمليات داخل الكلاس
حسنًا أظنك لم تقتنع بعد .. حسنًا فكر معي
الآن لدينا مكان يحتوي على المؤلفين وأشبه بـ Database
وكل تعاملك مع المؤلفين تكون من خلال هذا الـ Database
سواء كان إضافة مؤلف جديد أو البحث عن مؤلف معين أو تعديل بياناته أو حذفه
ولكن كيف يمكننا القيام ببعض العمليات عليهم ؟
فمثلًا نريد أن نقوم بالبحث عن مؤلف بالـ id
أو بالـ name
أو بالـ email
هنا سأقول لك بكل بساطة أنك يمكنك إضافة دوال للكلاس تقوم بالبحث عن المؤلف بناءً على الـ id
أو الـ name
أو الـ email
class AuthorDatabase {
// ...
public static getAuthorById(id: number): Author | null {
return this.authors.find(author => author.getId() === id);
}
public static getAuthorByName(name: string): Author | null {
return this.authors.find(author => author.name === name);
}
public static getAuthorByEmail(email: string): Author | null {
return this.authors.find(author => author.email === email);
}
}
لاحظ أننا في كلاس الـ AuthorDatabase
قمنا بإضافة دوال تقوم بالبحث عن المؤلف بناءً على الـ id
أو الـ name
أو الـ email
وهذه الدوال تقوم بإرجاع المؤلف إذا وجده أو null
إذا لم يجده
وبهذا يمكنك الآن القيام بالبحث عن مؤلف معين بناءً على الـ id
أو الـ name
أو الـ email
const author = AuthorDatabase.getAuthorById(1);
if (author) {
console.log(author.name);
} else {
console.log('Author not found');
}
وطالما لدينا مكان موحد به كل المؤلفين ويمكننا القيام بالعمليات عليهم بسهولة
فلاداعي لأن اغششك وأقول لك أنك يمكنك عمل دوال متنوعة أخرى مثل الحذف أو التعديل وأي شيء تريده داخل كلاس الـ AuthorDatabase
كلاس الـ AuthorDatabase
يعد مثال جيد وبسيط لفكرة الـ Abstraction
وهو اخفاء تفاصيل الـ Implementation
والعمليات التي تدور داخل الكلاس عن المستخدم الذي سيستخدم الكلاس
أظننا أنتهينا من الكلاس الأول الذي يمثل المؤلف، والآن سننتقل إلى الكلاس الثاني الذي يمثل المقالات
إنشاء كلاس المقالات
بناءً على ما قمنا به مع كلاس Author
, دعنا نطبق نفس الشيءمع كلاس Article
يمكنك أن تجلس وتفكر في الخصائص التي يجب أن يحتويها الكلاس
ستقول أنه يجب أن يحتوي على متغيرات مثل: العنوان، المحتوى، تاريخ النشر، ومؤلف المقال ... إلخ
لكن سؤال كيف يمكننا ربط المقال بالمؤلف ؟
هنا سنقوم بإضافة متغير يدعى authorId
يحتوي على id
للمؤلف المرتبط بالمقالة
وعند إنشاء المقالة الجديدة يجب عليك تمرير id
للمؤلف المرتبط بها
class Article {
private static lastId: number = 0;
private id: number;
public title: string;
public content: string;
private authorId: number;
private publishedAt: Date | null;
constructor(title: string, content: string, authorId: number) {
this.title = title;
this.content = content;
this.authorId = authorId;
this.publishedAt = null;
this.setGeneratedId();
}
private setGeneratedId() {
Article.lastId++;
this.id = Article.lastId;
}
public getId(): number {
return this.id;
}
public getAuthorId(): number {
return this.authorId;
}
public publish(): void {
this.publishedAt = new Date();
// تخيل هنا كود جميل يقوم بنشر المقالة على موقع ما
}
}
ستلاحظ عدة أمور منها:
- أن الكلاس يحتوى على نفس الأمور قمنا بها مع الكلاس
Author
مثل توليدid
تلقائيًا - وستلاحظ أننا قمنا بإضافة متغير يدعى
publishedAt
يحتوي على تاريخ النشر وهوnull
بشكل افتراضي
ومتغير الـpublishedAt
هنا سيكون بالطبعprivate
لأنه لا يجب أن يتم تعديله من خارج الكلاس - وستلاحظ بالطبع أننا قمنا بإضافة دالة
publish
تقوم بتعين قيمة تاريخ النشر للمتغيرpublishedAt
ونشر المقالة بشكل عام - وبالطبع لدينا المتغيرات الأساسية مثل
title
وcontent
وauthorId
الشيء المثير هو هو الـ authorId
الذي يحتوي على id
للمؤلف المرتبط بالمقالة
وهذا الـ authorId
ستجده انه private
لأنه لا يجب أن يتم تعديله من خارج الكلاس .. نفس الفكرة التي قمنا بها مع الـ id
لذا ستجد أننا قمنا بعمل getter
للـ authorId
وهي دالة الـ getAuthorId
التي تقوم بإرجاع الـ authorId
فقط
لكن سؤال ... كيف سيفيدنا هذا الـ authorId
في ربط المقال بالمؤلف ؟
لنبدأ أولًا بإنشاء المؤلف وإضافته إلى قاعدة البيانات
const author = AuthorDatabase.createAuthor('Ahmed', '[email protected]', 'Backend Developer at Quotem');
الآن لنقم بإنشاء مقال جديد وربطه بالمؤلف
const article = new Article('Introduction to OOP', 'This is an introduction to OOP', author.getId());
هنا لدينا مقال جديد يحتوي على title
و content
و authorId
وهذا الـ authorId
يحتوي على id
للمؤلف المرتبط به
الآن ستلاحظ شيئن مهمين وهما أننا يمكنك أن نرسل أي رقم للـ authorId
حتى وإن كان لا يوجد مؤلف بهذا الـ id
والشيئ التاني كيف نقوم بعمل عمليات على المقالات مثل البحث عن مقال معين أو تعديل بياناته أو حذفه
أو حتى أحضار بيانات المؤلف المرتبط به لأننا ربطنا المقال بالمؤلف بالـ id
فقط
الحلول كثيرة جدًا ومتنوعة لكن دعونا أولًا نلتزم بما فعلناه مع الـ Author
ونقوم بإنشاء قاعدة بيانات خاصة للمقالات
تخزين المقالات في قاعدة البيانات
سنقوم بإنشاء كلاس ArticleDatabase
مشابه لكلاس AuthorDatabase
لتخزين المقالات
class ArticleDatabase {
private static articles: Article[] = [];
private constructor() {}
public static addArticle(article: Article): void {
this.articles.push(article);
}
public static createArticle(title: string, content: string, authorId: number): Article {
// التحقق من وجود المؤلف
const author = AuthorDatabase.getAuthorById(authorId);
if (!author) {
throw new Error('Author not found');
}
const article = new Article(title, content, authorId);
this.articles.push(article);
return article;
}
}
هنا قمنا بإنشاء كلاس ArticleDatabase
يحتوي على أراي من الـ Article
تمثل المقالات
وقمنا بإضافة دالة addArticle
تقوم بإضافة مقال جديد إلى قاعدة البيانات
ثم أهم شيء هي دالة createArticle
التي تقوم بإنشاء مقال جديد وربطه بالمؤلف المرتبط به
لكن ستلاحظ أنها تقوم بشيء مهم جدًا وهو التحقق من وجود المؤلف
بمعنى هل الـ authorId
المرسل موجود بالفعل في قاعدة بيانات المؤلفين ؟
استخدمنا دالة getAuthorById
التي قمنا بإضافتها في كلاس ال AuthorDatabase
التي تمثل قاعدة بيانات المؤلفين للتحقق من وجود المؤلف
وإذا لم يكن موجودًا فسيتم رمي Exception
الآن يمكنك إنشاء مقال جديد وربطه بالمؤلف بشكل مباشر
const author = AuthorDatabase.createAuthor('Ahmed', '[email protected]', 'Backend Developer at Quotem');
const article = ArticleDatabase.createArticle('Introduction to OOP', 'This is an introduction to OOP', author.getId());
وهكذا يمكنك الآن إنشاء مقال جديد وربطه بالمؤلف بشكل مباشر وبسهولة
عمل بعض العمليات داخل الكلاس
المشكلة التانية التي تكلمنا عنها هي كيف يمكننا القيام ببعض العمليات على المقالات مثل البحث عن مقال معين أو تعديل بياناته أو حذفه
وخصوصًا كيف يمكننا الحصول على بيانات المؤلف المرتبط به
الأمر بسيط جدًا ومشابه لما قمنا به مع الـ AuthorDatabase
class ArticleDatabase {
// ...
public static getArticleById(id: number): Article | null {
return this.articles.find(article => article.getId() === id);
}
public static getArticleByAuthorId(authorId: number): Article[] {
return this.articles.filter(article => article.getAuthorId() === authorId);
}
public static getAuthorByArticleId(articleId: number): Author | null {
const article = this.getArticleById(articleId);
if (!article) {
return null;
}
return AuthorDatabase.getAuthorById(article.authorId);
}
}
هنا قمنا بإضافة دوال تقوم بالبحث عن المقال بناءً على الـ id
أو الـ title
أو الـ authorId
وتلك الدوال البسيطة التي قمنا بعمل ما يشبهها في كلاس الـ AuthorDatabase
لكن الجديد هنا هي دالة getAuthorByArticleId
التي تقوم بإرجاع المؤلف المرتبط بالمقالة المعينة
ستلاحظ أنها تستقبل id
الخاص بالمقالة ثم تقوم بالتأكد من وجود المقالة أولًا عن طريق دالة getArticleById
ثم في النهاية عندما نحصل على object
المقالة يمكننا ارسال المتغير authorId
الخاص بها إلى دالة getAuthorById
للحصول على المؤلف
وبهذا يمكنك الآن القيام بالبحث عن مقال معين أو الحصول على بيانات المؤلف المرتبط به بشكل سهل
const article = ArticleDatabase.getArticleById(1);
const author = ArticleDatabase.getAuthorByArticleId(article.getId());
وهكذا يمكنك الآن القيام بالبحث عن مقال معين أو الحصول على بيانات المؤلف المرتبط به بشكل سهل
يمكننا عمل فكرة أخرى وهي بدلاً من استخدام دالة الـ getAuthorByArticleId
المتواجدة في كلاس الـ ArticleDatabase
لماذا لا نقوم بعمل دالة جديدة في كلاس الـ Article
تقوم بإرجاع المؤلف المرتبط بها ؟
class Article {
// ...
public getAuthor(): Author | null {
return AuthorDatabase.getAuthorById(this.authorId);
}
}
هكذا بكل بساطة يمكننا احضار بيانات المؤلف المرتبط بالمقالة بشكل مباشر من الـ object
نفسه
const article = ArticleDatabase.getArticleById(1);
const author = article.getAuthor();
هكذا يمكنك الآن القيام بالبحث عن مقال معين من خلال الـ ArticleDatabase
ويمكننا الآن الحصول على بيانات المؤلف المرتبط به من خلال الـ object
نفسه عن طريق دالة getAuthor
حسنًا ... أظن أن هذا كل شيء أردت أن أشرحه لك في مثال تطبيق مدونة بسيطة
المثال غرضه الشرح لا أكثر ولا أقل لذا لا أريد أن أسمعك تقول لي ... نحن لم نقم بإنشاء نظام مدونة بشكل متكامل
أنا فقط أردت أن أفكر معك ونحلل ونشرح بعض الأفكار والمفاهيم الأساسية للـ OOP
بشكل عملي أكثر
تقليل التكرار في الكود
أولًا ستلاحظ أن هناك تكرار لبعض الأمور في كلاس الـ Author
والـ Article
ومن ضمنها توليد id
ودالة setGeneratedId
لذا يمكننا أن نقوم بعمل كلاس يحتوي على هذه الأمور ونرث منه في الكلاسات الأخرى
abstract class IdGenerator {
private static lastId: number = 0;
private id: number;
constructor() {
this.setGeneratedId();
}
private setGeneratedId() {
IdGenerator.lastId++;
this.id = IdGenerator.lastId;
}
public getId(): number {
return this.id;
}
}
هنا قمنا بعمل كلاس IdGenerator
يحتوي على توليد id
ودالة setGeneratedId
ودالة getId
الأمور الأساسية الخاصة بالـ id
والتي يمكننا أن نرث منها في الكلاسات الأخرى
ولاحظ أننا جعلنا الكلاس IdGenerator
يكون abstract
لأنه لا يجب أن يتم إنشاء object
منه، فقط يمكنك أن ترثه لتحصل على المميزات التي يوفرها
ولاحظ أننا قمنا بعمل constructor
له لكي يتم توليد id
تلقائيًا عند إنشاء object
من الكلاس الذي سيرث منه
ثم يمكننا أن نرث منه في الكلاسات الأخرى
class Author extends IdGenerator {
public name: string;
public email: string;
public bio: string;
constructor(name: string, email: string, bio: string) {
super();
this.name = name;
this.email = email;
this.bio = bio;
}
}
class Article extends IdGenerator {
public title: string;
public content: string;
private authorId: number;
private publishedAt: Date | null;
constructor(title: string, content: string, authorId: number) {
super();
this.title = title;
this.content = content;
this.authorId = authorId;
this.publishedAt = null;
}
}
وكما ترى الآن يمكننا أن نرث من الكلاس IdGenerator
في الكلاسات الأخرى ونحصل على توليد id
تلقائيًا
دون الحاجة لتكرار الكود في كل كلاس أو الاهتمام بهذه التفاصيل كل مرة
وهذا يعد مثال جيد على فكرة الـ Abstraction
والتي تعني إخفاء التفاصيل الداخلية للـ Implementation
وفكرة الـ Inheritance
والتي تعني إعادة استخدام الكود وتقليل التكرار
أهم جزء هنا هو جزء الـ super()
الذي نستدعيه في constructor
كل كلاس يرث من الـ IdGenerator
لكي ينادي ويستدعي الـ constructor
الخاص بالكلاس الأب ويتم توليد id
تلقائيًا
لأن الـ super()
يمثل الـ constructor
الخاص كلاس IdGenerator
وبالطبع طريقة الاستدعاء تختلف من لغة للأخرى ولكن الفكرة هي نفسها فمثلًا في لغة PHP
يمكنك استدعاء parent::__construct()
يوجد شيء آخر يمكننا أن نفعله وهو عمل interface
للـ Author
والـ Article
لماذا ؟ لكي نميز بين الكلاسات وما بعضها فمثلًا الـ Author
والـ Article
هما كيانات لا يربطها شيء سوى أنهما كيانات يمكننا إنشاء منهما object
ويحتويان على دوال متنوعة مثل getId
لذا نحتاج لشيء لربط هذا النوع من الكلاسات لذا سنستفيد من الـ interface
وننشيء واحد يسمى Model
على سبيل المثال أو Entity
interface Model {
getId(): number;
printInfo(): void;
}
class Author extends IdGenerator implements Model {
// ...
public printInfo(): void {
console.log(`Author: ${this.name} - ${this.email}`);
}
}
class Article extends IdGenerator implements Model {
// ...
public printInfo(): void {
console.log(`Article: ${this.title} by ${this.getAuthor().name}`);
}
}
الآن الفائدة هنا أننا نعرف أن كلاس Author
و Article
ينتميان لنفس العائلة وهى عائلة الـ Model
وأنهما يحتويان على دوال مشتركة مثل getId
و printInfo
أو أي دالة أخرى مشتركة على مستوى كل Model
وهنا يمكننا تطبيق فكرة الـ Polymorphism
const model_1 : Model = new author('Ahmed', '[email protected]', 'Backend Developer at Quotem');
const model_2 : Model = new article('Introduction to OOP', 'This is an introduction to OOP', model_1.getId());
لاحظ أننا هنا قمنا بإنشاء object
من الـ Author
والـ Article
ولكن قمنا بتخزينهما في متغير من نوع Model
لأن كلاس Author
و Article
ينتميان لنفس العائلة وهي عائلة الـ Model
بالتالي يمكننا عمل متغير من نوع Model
وتخزين فيه أي object
من الـ Author
أو الـ Article
أو يمكننا عمل array
من الـ Model
وتخزين فيه الـ Author
والـ Article
const models: Model[] = [];
models.push(new author('Ahmed', '[email protected]', 'Backend Developer at Quotem'));
models.push(new article('Introduction to OOP', 'This is an introduction to OOP', 1));
for (const model of models) {
model.printInfo();
}
هناك أفكار وفوائد كثيرة جدًا للـ interface
والـ Polymorphism
سنتطرق لها في الأمثلة القادمة
ماذا عن الـ ArticleDatabase
و AuthorDatabase
؟
هل يمكننا عمل abstract class
ورث منها الـ ArticleDatabase
و AuthorDatabase
؟ أو حتى Interface
مشترك يكون لهما ؟
مثل ما قمنا به مع الـ Author
والـ Article
؟
بصراحة أجل ... هناك Design Pattern
يسمى Repository Pattern
يمكنك أن تقوم بتطبيقه هنا
وهو يعني أنك تقوم بعمل interface
أو abstract class
تحتوي على الدوال الأساسية التي يجب أن تحتوي عليها أي قاعدة بيانات
interface Repository {
add(data: Model): void;
getAll(): Model[];
getById(id: number): Model | null;
}
abstract class Database implements Repository {
protected static data: Model[] = [];
public static add(data: Model): void {
this.data.push(data);
}
public static getAll(): Model[] {
return this.data;
}
public static getById(id: number): Model | null {
return this.data.find(item => item.getId() === id);
}
}
هنا قمنا بعمل interface
يدعى Repository
يحتوي على الدوال الأساسية التي يجب أن تحتوي عليها أي قاعدة بيانات
ثم قمنا بعمل abstract class
يدعى Database
يقوم بتنفيذ الـ Repository
ويحتوي على الدوال الأساسية التي يجب أن تحتوي عليها أي قاعدة بيانات
ستلاحظ هنا أمور جميلة ومنها أننا استخدمنا Model
بدلاً من Author
و Article
هكذا نحن عممنا الأمور وجعلنا الـ Database
يعمل مع أي كلاس يرث من الـ Model
وأيضًا خصصناه ليعمل مع أي كلاس من نوع Model
فقط
وهذه من فوائد أننا استخدمنا الـ interface
يدعى Model
لربط مجموعة من الكلاسات معًا مثل الـ Author
والـ Article
لكي نستطيع الاستفادة منها كما رأينا سابقًا وكما رأينا هنا في كلاس الـ Database
هكذا يمكنك الآن أن تقوم بعمل AuthorDatabase
و ArticleDatabase
وتجعلهما يرثان من الـ Database
والتﻻ تحتوى على الأمور والدوال المشتركة بينهما
class AuthorDatabase extends Database {
// ...
public static getByEmail(email: string): Author | null {
return this.data.find(author => author.email === email);
}
}
class ArticleDatabase extends Database {
// ...
public static getByAuthorId(authorId: number): Article[] {
return this.data.filter(article => article.getAuthorId() === authorId);
}
}
هنا كما ترى فقط قمنا بعمل AuthorDatabase
و ArticleDatabase
ورثنا من الـ Database
دوال الـ Repository
الأساسية مثل add
و getAll
و getById
وأيضًا هذا لا يمنع من إضافة دوال جديدة خاصة بكل كلاس مثل getByEmail
في AuthorDatabase
و getByAuthorId
في ArticleDatabase
الختام
بالطبع هناك أفكار وأمور كثيرة يمكننا أن نقوم بها ونطبقها في هذا المثال البسيط
لكن أظنني سأكتفي بهذا القدر لأنني أردت أن أشرح لك الأمور الأساسية والمهمة في الـ OOP
أظننا قد طبقنا عدة مفاهيم وأفكار في هذا المثال البسيط والذي يعتبر مثال جيد للـ OOP
والتفكير فيها وتطبيقها بشكل عملي
وأظن أننا مررنا كل المباديء الأساسية للـ OOP
والتي يجب أن تكون على دراية بها
الهدف من هذه المقالة هو اعطائك نبذة ولو بسيطة في كيفية توظيف مباديء الـ OOP
في تطبيقاتك اليومية
كنت أود أن أشرح لك المزيد ولكن أظنني أنني قد قمت بشرح الأمور الأساسية والمهمة
يمكنك تاليًا قراءة المقالة المتعلقة بمباديء الـ SOLID
من هنا مبادئ الـ SOLID لجعل كودك صلب كالحديد