পূর্ববর্তী অংশ:
আমরা আমাদের কোডকে পরিষ্কার, নমনীয় এবং রক্ষণাবেক্ষণযোগ্য করার পন্থা বিবেচনা করে চলেছি। এবং এখন, এর পরিষ্কার স্থাপত্য সমাধান তদন্ত শুরু করা যাক.
সলিড নীতিগুলি রবার্ট সি. মার্টিন দ্বারা প্রবর্তিত হয়েছিল এবং অবজেক্ট-ওরিয়েন্টেড প্রোগ্রামিং এবং সফ্টওয়্যার ডিজাইনের সর্বোত্তম অনুশীলন হিসাবে ব্যাপকভাবে বিবেচিত হয়।
এই নিবন্ধে, আমরা তিনটি প্রথম সলিড নীতির দিকে নজর দেব: একক দায়িত্ব নীতি, খোলা/বন্ধ নীতি এবং ব্যবহারিক উদাহরণগুলিতে লিসকভ প্রতিস্থাপন নীতি।
একটি ক্লাস পরিবর্তন করার শুধুমাত্র একটি কারণ থাকা উচিত
অন্য কথায়, একটি শ্রেণীর একটি একক দায়িত্ব বা কাজ থাকা উচিত। এটি গুরুত্বপূর্ণ কারণ একটি একক দায়িত্ব সহ একটি ক্লাস বোঝা, পরিবর্তন করা এবং বজায় রাখা সহজ। কোডের একটি ক্ষেত্রের পরিবর্তনগুলি পুরো সিস্টেমে ঢেউ খেলানো হবে না, বাগ প্রবর্তনের ঝুঁকি হ্রাস করবে।
আসুন একটি বাস্তব উদাহরণ এবং একক দায়িত্ব নীতি অনুসরণ করার উপায়গুলি একবার দেখে নেওয়া যাক:
// Bad class UserSettingsService { constructor(user: IUser) { this.user = user; } changeSettings(settings: IUserSettings): void { if (this.isUserValidated()) { // ... } } getUserInfo(): Promise<IUserSettings> { // ... } async isUserValidated(): Promise<boolean> { const userInfo = await this.getUserInfo(); // ... } }
এই উদাহরণে, আমাদের শ্রেণী বিভিন্ন দিকে ক্রিয়া সম্পাদন করে: এটি প্রসঙ্গ সেট আপ করে, এটি পরিবর্তন করে এবং এটি বৈধ করে।
SRP অনুসরণ করার জন্য, আমাদের এই বিভিন্ন দায়িত্ব ভাগ করতে হবে।
// Better class UserAuth { constructor(user: IUser) { this.user = user; } getUserInfo(): Promise<IUserSettings> { // ... } async isUserValidated(): boolean { const userInfo = await this.getUserInfo(); // ... } } class UserSettings { constructor(user: IUser) { this.user = user; this.auth = new UserAuth(user); } changeSettings(settings: IUserSettings): void { if (this.auth.isUserValidated()) { // ... } } }
SRP-এর লক্ষ্য হল কোড মডুলারিটি উন্নত করা, আন্তঃ-নির্ভরতা থেকে উদ্ভূত চ্যালেঞ্জগুলি কমিয়ে আনা। কোডগুলিকে ফাংশনে সংগঠিত করে, আলাদা করা ক্লাস, এবং মডুলারিটি প্রচার করে, এটি আরও পুনঃব্যবহারযোগ্য হয়ে ওঠে, সময় সাশ্রয় করে যা অন্যথায় বিদ্যমান কার্যকারিতা পুনরায় কোড করার জন্য ব্যয় হতে পারে।
সফ্টওয়্যার সত্তা (ক্লাস, মডিউল, ফাংশন) এক্সটেনশনের জন্য খোলা থাকা উচিত কিন্তু পরিবর্তনের জন্য বন্ধ করা উচিত
আপনি বিদ্যমান কোড পরিবর্তন না করে নতুন কার্যকারিতা যোগ করতে সক্ষম হওয়া উচিত। এটি গুরুত্বপূর্ণ কারণ এই নীতি মেনে চলার মাধ্যমে, আপনি বিদ্যমান কোডের স্থায়িত্বকে ঝুঁকি না নিয়ে আপনার সিস্টেমে নতুন বৈশিষ্ট্য বা উপাদান প্রবর্তন করতে পারেন।
এটি কোড পুনঃব্যবহারযোগ্যতা প্রচার করে এবং কার্যকারিতা প্রসারিত করার সময় ব্যাপক পরিবর্তনের প্রয়োজনীয়তা হ্রাস করে।
// Bad class Product { id: number; name: string[]; price: number; protected constructor(id: number, name: string[], price: number) { this.id = id; this.name = name; this.price = price; } } class Ananas extends Product { constructor(id: number, name: string[], price: number) { super(id, name, price); } } class Banana extends Product { constructor(id: number, name: string[], price: string) { super(id, name, price); } } class HttpRequestCost { constructor(product: Product) { this.product = product; } getDeliveryCost(): number { if (product instanceOf Ananas) { return requestAnanas(url).then(...); } if (product instanceOf Banana) { return requestBanana(url).then(...); } } } function requestAnanas(url: string): Promise<ICost> { // logic for ananas } function requestBanana(url: string): Promise<ICost> { // logic for bananas }
এই উদাহরণে, সমস্যাটি হল HttpRequestCost
ক্লাসে, যেটি পদ্ধতিতে, getDeliveryCost
বিভিন্ন ধরনের পণ্যের গণনার শর্ত ধারণ করে এবং আমরা প্রতিটি ধরনের পণ্যের জন্য পৃথক পদ্ধতি ব্যবহার করি। সুতরাং, যদি আমাদের একটি নতুন ধরনের পণ্য যোগ করার প্রয়োজন হয়, তাহলে আমাদের HttpRequestCost
ক্লাসটি সংশোধন করা উচিত এবং এটি নিরাপদ নয়; আমরা অপ্রত্যাশিত ফলাফল পেতে পারি।
এটি এড়াতে, আমাদের উপলব্ধি ছাড়াই Product
শ্রেণিতে একটি বিমূর্ত পদ্ধতির request
তৈরি করা উচিত। বিশেষ উপলব্ধি ক্লাসগুলি উত্তরাধিকারসূত্রে প্রাপ্ত হবে: আনানস এবং কলা। তারা নিজেদের জন্য অনুরোধ বুঝতে হবে.
HttpRequestCost
Product
ক্লাস ইন্টারফেস অনুসরণ করে product
প্যারামিটার নেবে, এবং যখন আমরা HttpRequestCost
এ নির্দিষ্ট নির্ভরতা পাস করি, তখন এটি ইতিমধ্যে নিজের জন্য request
পদ্ধতি উপলব্ধি করবে।
// Better abstract class Product { id: number; name: string[]; price: string; constructor(id: number, name: string[], price: string) { this.id = id; this.name = name; this.price = price; } abstract request(url: string): void; } class Ananas extends Product { constructor(id: number, name: string[], price: string) { super(id, name, price); } request(url: string): void { // logic for ananas } } class Banana extends Product { constructor(id: number, name: string[], price: string) { super(id, name, price); } request(url: string): void { // logic for bananas } } class HttpRequestCost { constructor(product: Product) { this.product = product; } request(): Promise<void> { return this.product.request(url).then(...); } }
এই নীতি অনুসরণ করে, আপনি কোড কাপলিং কমাবেন এবং সিস্টেমটিকে অপ্রত্যাশিত আচরণ থেকে বাঁচাবেন।
একটি সুপারক্লাসের অবজেক্টগুলিকে প্রয়োগ না করেই এর সাবক্লাসের বস্তুর সাথে প্রতিস্থাপনযোগ্য হওয়া উচিত।
এই নীতিটি বোঝার জন্য, আসুন এই উদাহরণটি একবার দেখে নেওয়া যাক:
// Bad class Worker { work(): void {/../} access(): void { console.log('Have an access to closed perimeter'); } } class Programmer extends Worker { createDatabase(): void {/../} } class Seller extends Worker { sale(): void {/../} } class Designer extends Worker { access(): void { throwError('No access'); } }
এই উদাহরণে, আমাদের Contractor
শ্রেণীর সাথে একটি সমস্যা আছে। Designer
, Programmer
এবং Seller
সবাই কর্মী, এবং তারা পিতামাতা শ্রেণির Worker
থেকে উত্তরাধিকার সূত্রে প্রাপ্ত। কিন্তু একই সময়ে, ডিজাইনারদের বদ্ধ পরিধিতে অ্যাক্সেস নেই কারণ তারা ঠিকাদার, কর্মচারী নয়। এবং আমরা access
মেথড ওভাররাইড করেছি এবং লিসকভ সাবস্টিটিউশন প্রিন্সিপল ভঙ্গ করেছি।
এই নীতিটি আমাদের বলে যে আমরা যদি সুপারক্লাস Worker
এর সাবক্লাস দিয়ে প্রতিস্থাপন করি, উদাহরণস্বরূপ Designer
ক্লাস, কার্যকারিতা ভাঙা উচিত নয়। কিন্তু যদি আমরা এটি করি, Programmer
ক্লাসের কার্যকারিতা ভেঙে যাবে - access
পদ্ধতিতে Designer
ক্লাস থেকে অপ্রত্যাশিত উপলব্ধি হবে।
লিসকভ সাবস্টিটিউশন নীতি অনুসরণ করে, আমাদের উপলব্ধিগুলিকে সাবক্লাসে পুনর্লিখন করা উচিত নয় তবে বিমূর্ততার নতুন স্তর তৈরি করতে হবে যেখানে আমরা প্রতিটি ধরণের বিমূর্ততার জন্য নির্দিষ্ট উপলব্ধিকে সংজ্ঞায়িত করি।
আসুন এটি সংশোধন করা যাক:
// Better class Worker { work(): void {/../} } class Employee extends Worker { access(): void { console.log('Have an access to closed perimeter'); } } class Contractor extends Worker { addNewContract(): void {/../} } class Programmer extends Employee { createDatabase(): void {/../} } class Saler extends Employee { sale(): void {/../} } class Designer extends Contractor { makeDesign(): void {/../} }
আমরা Employee
এবং Contractor
বিমূর্তকরণের নতুন স্তর তৈরি করেছি এবং Employee
শ্রেণিতে access
পদ্ধতি স্থানান্তর করেছি এবং নির্দিষ্ট উপলব্ধি সংজ্ঞায়িত করেছি। আমরা যদি Worker
শ্রেণিকে সাবক্লাস Contractor
দিয়ে প্রতিস্থাপন করি, তাহলে Worker
কার্যকারিতা নষ্ট হবে না।
আপনি যদি LSP মেনে চলেন, আপনি যে কোনো সাবক্লাস ইনস্ট্যান্স প্রতিস্থাপন করতে পারেন যেখানে একটি বেস ক্লাস ইনস্ট্যান্স প্রত্যাশিত, এবং প্রোগ্রামটি এখনও উদ্দেশ্য অনুযায়ী কাজ করা উচিত। এটি কোড পুনঃব্যবহার, মডুলারিটি প্রচার করে এবং আপনার কোডকে পরিবর্তনের জন্য আরও স্থিতিস্থাপক করে তোলে।
পরের প্রবন্ধে, আমরা ইন্টারফেস সেগ্রিগেশন এবং ডিপেনডেন্সি ইনভার্সন সলিড নীতিগুলির দিকে নজর দেব।