এটি ফ্রেজিলে বেস ক্লাসের সমস্যা এড়িয়ে চলে । প্রতিটি ক্লাসই স্পষ্ট বা স্পষ্ট গ্যারান্টি এবং আক্রমণকারীদের একটি সেট নিয়ে আসে। লিসকভ সাবস্টিটিউশন নীতিমালা আদেশ দেয় যে class শ্রেণীর সমস্ত উপ-প্রকারকে অবশ্যই এই সমস্ত গ্যারান্টি সরবরাহ করতে হবে। তবে, যদি আমরা এটি ব্যবহার না করি তবে এটি লঙ্ঘন করা সত্যিই সহজ final
। উদাহরণস্বরূপ, আসুন একটি পাসওয়ার্ড পরীক্ষক থাকি:
public class PasswordChecker {
public boolean passwordIsOk(String password) {
return password == "s3cret";
}
}
আমরা যদি সেই শ্রেণিকে ওভাররাইড করার অনুমতি দিই, একটি বাস্তবায়ন প্রত্যেককে লক আউট করতে পারে, অন্য একজন সবাইকে অ্যাক্সেস দিতে পারে:
public class OpenDoor extends PasswordChecker {
public boolean passwordIsOk(String password) {
return true;
}
}
এটি সাধারণত ঠিক থাকে না, যেহেতু সাবক্লাসগুলির আচরণ এখন মূলের সাথে খুব বেমানান। আমরা যদি সত্যই শ্রেণীর অন্যান্য আচরণের সাথে প্রসারিত করার ইচ্ছা করি, তবে দায়বদ্ধতার একটি চেইন আরও ভাল হবে:
PasswordChecker passwordChecker =
new DefaultPasswordChecker(null);
// or:
PasswordChecker passwordChecker =
new OpenDoor(null);
// or:
PasswordChecker passwordChecker =
new DefaultPasswordChecker(
new OpenDoor(null)
);
public interface PasswordChecker {
boolean passwordIsOk(String password);
}
public final class DefaultPasswordChecker implements PasswordChecker {
private PasswordChecker next;
public DefaultPasswordChecker(PasswordChecker next) {
this.next = next;
}
@Override
public boolean passwordIsOk(String password) {
if ("s3cret".equals(password)) return true;
if (next != null) return next.passwordIsOk(password);
return false;
}
}
public final class OpenDoor implements PasswordChecker {
private PasswordChecker next;
public OpenDoor(PasswordChecker next) {
this.next = next;
}
@Override
public boolean passwordIsOk(String password) {
return true;
}
}
সমস্যাটি আরও স্পষ্ট হয়ে ওঠে যখন আরও জটিল শ্রেণি তার নিজস্ব পদ্ধতিগুলি কল করে এবং সেই পদ্ধতিগুলি ওভাররাইড করা যায়। ডেটা স্ট্রাকচারের প্রিন্ট-প্রিন্টিং বা এইচটিএমএল লেখার সময় আমি কখনও কখনও এটির মুখোমুখি হই। প্রতিটি পদ্ধতি কিছু উইজেটের জন্য দায়ী।
public class Page {
...;
@Override
public String toString() {
PrintWriter out = ...;
out.print("<!DOCTYPE html>");
out.print("<html>");
out.print("<head>");
out.print("</head>");
out.print("<body>");
writeHeader(out);
writeMainContent(out);
writeMainFooter(out);
out.print("</body>");
out.print("</html>");
...
}
void writeMainContent(PrintWriter out) {
out.print("<div class='article'>");
out.print(htmlEscapedContent);
out.print("</div>");
}
...
}
আমি এখন একটি সাবক্লাস তৈরি করেছি যা আরও কিছু স্টাইলিং যুক্ত করেছে:
class SpiffyPage extends Page {
...;
@Override
void writeMainContent(PrintWriter out) {
out.print("<div class='row'>");
out.print("<div class='col-md-8'>");
super.writeMainContent(out);
out.print("</div>");
out.print("<div class='col-md-4'>");
out.print("<h4>About the Author</h4>");
out.print(htmlEscapedAuthorInfo);
out.print("</div>");
out.print("</div>");
}
}
এখন এই মুহুর্তের জন্য উপেক্ষা করা যে এইচটিএমএল পৃষ্ঠাগুলি উত্পন্ন করার খুব ভাল উপায় নয়, যদি আমি আবার লেআউটটি পরিবর্তন করতে চাই তবে কী হবে? আমাকে একটি SpiffyPage
সাবক্লাস তৈরি করতে হবে যা কোনওভাবে সেই সামগ্রীটিকে আবৃত করে। আমরা এখানে যা দেখতে পাচ্ছি তা হ'ল টেম্পলেট পদ্ধতি প্যাটার্নের একটি দুর্ঘটনাজনক অ্যাপ্লিকেশন। টেমপ্লেট পদ্ধতিগুলি একটি বেস শ্রেণিতে ভাল-সংজ্ঞায়িত এক্সটেনশন পয়েন্টগুলি ওভাররাইড করার উদ্দেশ্যে তৈরি।
এবং বেস ক্লাস পরিবর্তন হলে কী হবে? এইচটিএমএল বিষয়বস্তু যদি খুব বেশি পরিবর্তন হয় তবে এটি সাবক্লাসগুলির সরবরাহিত বিন্যাসটি ভেঙে দিতে পারে। সুতরাং বেস ক্লাসের পরে পরিবর্তন করা সত্যই নিরাপদ নয়। আপনার সমস্ত ক্লাস একই প্রকল্পে থাকলে এটি স্পষ্ট নয়, তবে বেস ক্লাসটি যদি অন্য প্রকাশিত সফ্টওয়্যারগুলির অংশ হয় যা অন্য লোকেরা তৈরি করে তবে এটি খুব লক্ষণীয়।
যদি এই এক্সটেনশান কৌশলটি উদ্দেশ্য করা হয়, তবে আমরা ব্যবহারকারীকে প্রতিটি অংশ কীভাবে তৈরি করা যায় তার উপায়গুলি অদলবদলের অনুমতি দিতে পারতাম। হয়, প্রতিটি ব্লকের জন্য কৌশল থাকতে পারে যা বাহ্যিকভাবে সরবরাহ করা যেতে পারে। বা, আমরা সজ্জিত বাসা করতে পারি। এটি উপরের কোডের সমতুল্য, তবে আরও সুস্পষ্ট এবং অনেক বেশি নমনীয়:
Page page = ...;
page.decorateLayout(current -> new SpiffyPageDecorator(current));
print(page.toString());
public interface PageLayout {
void writePage(PrintWriter out, PageLayout top);
void writeMainContent(PrintWriter out, PageLayout top);
...
}
public final class Page {
private PageLayout layout = new DefaultPageLayout();
public void decorateLayout(Function<PageLayout, PageLayout> wrapper) {
layout = wrapper.apply(layout);
}
...
@Override public String toString() {
PrintWriter out = ...;
layout.writePage(out, layout);
...
}
}
public final class DefaultPageLayout implements PageLayout {
@Override public void writeLayout(PrintWriter out, PageLayout top) {
out.print("<!DOCTYPE html>");
out.print("<html>");
out.print("<head>");
out.print("</head>");
out.print("<body>");
top.writeHeader(out, top);
top.writeMainContent(out, top);
top.writeMainFooter(out, top);
out.print("</body>");
out.print("</html>");
}
@Override public void writeMainContent(PrintWriter out, PageLayout top) {
... /* as above*/
}
}
public final class SpiffyPageDecorator implements PageLayout {
private PageLayout inner;
public SpiffyPageDecorator(PageLayout inner) {
this.inner = inner;
}
@Override
void writePage(PrintWriter out, PageLayout top) {
inner.writePage(out, top);
}
@Override
void writeMainContent(PrintWriter out, PageLayout top) {
...
inner.writeMainContent(out, top);
...
}
}
( ডেকোরেটর চেইনের শীর্ষে top
কলগুলি writeMainContent
যেতে হবে তা নিশ্চিত করার জন্য অতিরিক্ত প্যারামিটারটি প্রয়োজনীয় This এটি ওপেন পুনরাবৃত্তি নামক সাবক্লাসিংয়ের একটি বৈশিষ্ট্য অনুকরণ করে ))
আমাদের যদি একাধিক সজ্জকার থাকে তবে আমরা এখন আরও অবাধে মিশ্রিত করতে পারি।
বিদ্যমান কার্যকারিতাটি সামান্য মানিয়ে নেওয়ার আকাঙ্ক্ষার চেয়ে প্রায়শই প্রায়শই বিদ্যমান শ্রেণীর কিছু অংশ পুনরায় ব্যবহার করার ইচ্ছা। আমি এমন একটি কেস দেখেছি যেখানে কেউ ক্লাস চেয়েছিল যেখানে আপনি আইটেমগুলি যুক্ত করতে পারেন এবং সেগুলির উপরে পুনরাবৃত্তি করতে পারেন। সঠিক সমাধানটি হ'ল:
final class Thingies implements Iterable<Thing> {
private ArrayList<Thing> thingList = new ArrayList<>();
@Override public Iterator<Thing> iterator() {
return thingList.iterator();
}
public void add(Thing thing) {
thingList.add(thing);
}
... // custom methods
}
পরিবর্তে, তারা একটি সাবক্লাস তৈরি করেছে:
class Thingies extends ArrayList<Thing> {
... // custom methods
}
এর হঠাৎ এর অর্থ হ'ল পুরো ইন্টারফেসটি আমাদের ইন্টারফেসের ArrayList
অংশ হয়ে গেছে । ব্যবহারকারীরা নির্দিষ্ট জিনিসগুলিতে জিনিসগুলি বা জিনিসগুলি করতে পারেন । এটা কি এইভাবে উদ্দেশ্য ছিল? ঠিক আছে. তবে প্রায়শই, আমরা সমস্ত পরিণতিটি সাবধানতার সাথে চিন্তা করি না।remove()
get()
সুতরাং এটি পরামর্শ দেওয়া হয়
extend
সাবধান চিন্তা না করে কখনও ক্লাস করবেন না।
- আপনার ক্লাসটি সর্বদা চিহ্নিত করুন
final
যদি আপনি কোনও পদ্ধতির ওভাররাইড হওয়ার জন্য চান না।
- ইন্টারফেসগুলি তৈরি করুন যেখানে আপনি কোনও প্রয়োগকরণ অদলবদল করতে চান, যেমন ইউনিট পরীক্ষার জন্য।
অনেকগুলি উদাহরণ রয়েছে যেখানে এই "বিধি "টি ভাঙ্গতে হয়েছে, তবে এটি সাধারণত আপনাকে একটি ভাল, নমনীয় নকশার দিকে পরিচালিত করে এবং বেস ক্লাসে অনবচ্ছিন্ন পরিবর্তনের কারণে (বা বেস ক্লাসের উদাহরণ হিসাবে সাবক্লাসের অপ্রয়োজনীয় ব্যবহারের কারণে বাগগুলি এড়ানো হয়) )।
কিছু ভাষায় কঠোর প্রয়োগের ব্যবস্থা রয়েছে:
- সমস্ত পদ্ধতি ডিফল্টরূপে চূড়ান্ত হয় এবং এটি স্পষ্টভাবে চিহ্নিত করতে হবে
virtual
- তারা ব্যক্তিগত উত্তরাধিকার সরবরাহ করে যা ইন্টারফেসের উত্তরাধিকার সূত্রে প্রাপ্ত হয় না শুধুমাত্র বাস্তবায়ন।
- তাদের বেস ক্লাসের পদ্ধতিগুলি ভার্চুয়াল হিসাবে চিহ্নিত করা প্রয়োজন এবং সমস্ত ওভাররাইডগুলি পাশাপাশি চিহ্নিত করা প্রয়োজন। এটি এমন সমস্যাগুলিকে এড়িয়ে চলে যেখানে সাবক্লাস একটি নতুন পদ্ধতি সংজ্ঞায়িত করে, তবে একই স্বাক্ষরযুক্ত একটি পদ্ধতি পরে বেস শ্রেণিতে যুক্ত করা হয়েছিল তবে ভার্চুয়াল হিসাবে নয়।
final
? অনেক লোক (আমাকে সহ) দেখতে পান যে প্রতিটি নন-অ্যাবস্ট্রাক্ট বর্গ তৈরি করার জন্য এটি একটি ভাল নকশাfinal
।