کوئرینویسی در Django ORM؛ filter، annotate و روابط مدلها
Django ORM فقط راهی برای حذف نوشتن SQL نیست؛ باید زمان اجرای QuerySet، معنای اتصالها و هزینه خواندن روابط را …
اینترفیس در جاوا بیشتر برای بیان قرارداد قابلیت است؛ کلاس انتزاعی میتواند وضعیت، سازنده و اجرای مشترک یک خانواده را هم نگه دارد. این راهنما تفاوتها را با مثال، جدول، متد پیشفرض و انواع بسته توضیح میدهد تا انتخاب شما بر نیاز طراحی استوار باشد، نه یک جمله قدیمی و مطلق.
آخرین بهروزرسانی: ۱۷ آبان ۱۴۰۵ — منابع بررسیشده در این روز: آموزشهای رسمی اوراکل، فصلهای ۸ و ۹ مشخصات زبان جاوا ۲۵ و پیشنهاد رسمی ۴۰۹.
⚡ پاسخ کوتاه
برای قرارداد مشترک میان انواع، اینترفیس را بررسی کنید؛ برای وضعیت نمونه، سازنده و الگوریتم مشترک، کلاس انتزاعی را. یک کلاس فقط یک ابرکلاس مستقیم دارد ولی میتواند چند اینترفیس داشته باشد. متد default تفاوت را حذف نمیکند و تعارض پیشفرضها باید صریح حل شود.
📑 فهرست مطالب
وقتی چند نوع شیء باید رفتار مشترکی ارائه کنند، میتوانید مصرفکننده را به یک قرارداد وابسته کنید، نه به یک پیادهسازی مشخص. برای مثال، سرویس گزارش لازم نیست بداند گزارش در فایل ذخیره میشود یا جای دیگری؛ کافی است با قراردادی برای نوشتن متن کار کند. اینترفیس در جاوا برای بیان چنین قابلیتی مناسب است. پرسش اصلی این نیست که کدام دستور کوتاهتر است، بلکه این است که چه چیزی را میخواهید مشترک کنید.
کلاس انتزاعی علاوه بر تعریف رفتار مورد انتظار، میتواند وضعیت هر نمونه، سازنده و بخش مشترک الگوریتم را نگه دارد. اگر چند زیرکلاس واقعاً یک خانواده با چرخه حیات مشترکاند، این ابزار میتواند مناسب باشد. اما اگر فقط میخواهید بگویید انواع نامرتبط قابلیت مشخصی دارند، تحمیل یک کلاس پایه ممکن است آزادی طراحی را کم کند. انتخاب اشتباه معمولاً بعداً در آزمون، تغییر و ارثبری نمایان میشود.
در این مقاله، نمونهها برای قابلیتهای قابل استفاده در Java 21 و Java 25 نوشته شدهاند و ویژگیهای مورد استفاده به امکانات آزمایشی وابسته نیستند. در محیط نگارش، کامپایل و اجرای نمونههای جاوا انجام نشده است؛ بنابراین خروجی اجرا یا ادعای کارایی به آنها نسبت نمیدهیم. مرجع معنا و محدودیتهای زبان، مستندات رسمی و مشخصات زبان جاوا ۲۵ است. برای شروع پایهای، مقدمه زبان جاوا را مرور کنید.
متد انتزاعی قرارداد رفتار را بدون بدنه پیادهسازی تعریف میکند و پیادهسازی لازم باید در مسیر زیرکلاسهای مناسب کامل شود. خود کلاس انتزاعی مستقیماً نمونهسازی نمیشود. برای توضیح مفصلتر فراخوانی و متدهای انتزاعی، راهنمای فراخوانی متد در جاوا را بخوانید. اینجا بحث را روی تفاوت ابزارهای طراحی نگه میداریم تا آموزش قبلی را تکرار نکنیم.
| ویژگی | اینترفیس | کلاس انتزاعی |
|---|---|---|
| معنای معمول | قرارداد قابلیت | پایه یک خانواده با رفتار یا وضعیت مشترک |
| فیلد نمونه | ندارد؛ فیلدها عمومی، ایستا و نهاییاند | میتواند داشته باشد |
| سازنده | ندارد | دارد؛ هنگام ساخت زیرکلاس استفاده میشود |
| بدنه متد | پیشفرض، ایستا و خصوصی ممکن است | متد کامل و انتزاعی ممکن است |
| وراثت کلاس پیادهساز | میتواند چند اینترفیس را پیاده کند | فقط یک ابرکلاس مستقیم |
| کنترل گسترش | امکان sealed مطابق قواعد زبان | امکان sealed مطابق قواعد زبان |
| کارایی | بدون اندازهگیری، حکم عمومی نداریم | بدون اندازهگیری، حکم عمومی نداریم |
جدول، میانبری برای تصمیم است نه جایگزین فهم معنا. «داشتن پیادهسازی» دیگر مرز مطلق این دو نیست؛ اینترفیس میتواند متد پیشفرض، ایستا و خصوصی داشته باشد. تفاوت مهمتر، وضعیت هر نمونه، سازنده، قواعد وراثت و معنای رابطه است. هنگام خواندن آموزشهای قدیمی جاوا مراقب جمله «همه متدهای اینترفیس بدون بدنهاند» باشید؛ چنین جملهای تصویر درستی از جاوای امروزی نمیدهد.
کلاس انتزاعی میتواند فیلد نمونه و سازنده داشته باشد و هنگام ساخت زیرکلاس، بخش پایه را مقداردهی کند. اینترفیس فیلد نمونه و سازنده ندارد؛ فیلدهای اعلامشده در آن بهصورت ضمنی عمومی، ایستا و نهاییاند. بااینحال نهایی بودن یک مرجع، شیء اشارهشده را عمیقاً تغییرناپذیر نمیکند. قرار دادن یک مجموعه قابل تغییر در چنین فیلدی، راه خوبی برای ساخت وضعیت عمومی پنهان نیست.
یک کلاس فقط یک ابرکلاس مستقیم دارد، ولی میتواند چند اینترفیس را پیادهسازی کند. خود یک اینترفیس نیز میتواند چند اینترفیس را گسترش دهد. بنابراین اگر کلاسی از قبل به کلاس پایه دیگری نیاز دارد، اینترفیس همچنان راه بیان قابلیت اضافی را فراهم میکند. این انعطاف، دلیل مهم استفاده از قرارداد است؛ اما به معنی مجاز بودن چند پیادهسازی مبهم و بدون حل تعارض نیست.

فرض کنیم میخواهیم روش تولید گزارش از محل نوشتن آن جدا باشد. قرارداد کوچک زیر فقط رفتار موردنیاز مصرفکننده را میگوید. نام فایل در بالای هر قطعه، چیدمان پیشنهادی فایل عمومی را نشان میدهد؛ هر نوع عمومی در فایل همنام خودش قرار میگیرد. این نمونه آموزشی است و مدیریت خطای ورودی و خروجی، ذخیره واقعی و سیاست دسترسی تولید را پیادهسازی نمیکند.
// ReportSink.java
public interface ReportSink {
void write(String text);
}
// ConsoleSink.java
public final class ConsoleSink implements ReportSink {
@Override
public void write(String text) {
System.out.println(text);
}
}
// ReportService.java
public final class ReportService {
private final ReportSink sink;
public ReportService(ReportSink sink) {
this.sink = java.util.Objects.requireNonNull(sink);
}
public void publish(String text) {
sink.write(text);
}
}
مصرفکننده به قرارداد وابسته است؛ برای آزمون میتوانید یک پیادهسازی حافظهای بسازید و متن دریافتشده را بررسی کنید. این فایده از طراحی مرز میآید، نه از اضافه کردن واژه اینترفیس به هر کلاس. اگر دو نوع هیچ تفاوت معناداری ندارند و قراردادشان فقط یک نام اضافی است، شاید هنوز نیازی به این جداسازی نداشته باشید. قرارداد باید از یک تغییر یا وابستگی واقعی دفاع کند.
نوشتن گزارش، فشردهسازی و قابل مقایسه بودن، قابلیتهاییاند که میتوانند در انواع نامرتبط دیده شوند. در مقابل، اگر چند نوع واقعاً مراحل ثابت و وضعیت مشترک دارند، کلاس پایه مناسبتر به نظر میرسد. پیش از ارثبری بپرسید آیا زیرکلاس در همه موقعیتهای معتبر میتواند جای نوع پایه بنشیند؛ اگر با پرتاب خطا برای بیشتر رفتارهای پایه از آن فرار میکند، رابطه احتمالاً درست طراحی نشده است.
در مثال زیر، روند اصلی تولید گزارش ثابت است اما زیرکلاس محتوا را میسازد. عنوان، وضعیت نمونه است و سازنده کلاس پایه آن را دریافت میکند. متد اصلی نهایی شده تا زیرکلاس نظم مراحل را عوض نکند؛ این فقط یک انتخاب طراحی نمونه است، نه قانونی برای همه کلاسهای انتزاعی. برای فهم معنای واژهها نیز کلمات کلیدی جاوا را ببینید.
// AbstractReport.java
public abstract class AbstractReport {
private final String title;
protected AbstractReport(String title) {
this.title = java.util.Objects.requireNonNull(title);
}
public final String render() {
return title + System.lineSeparator() + body();
}
protected abstract String body();
}
// InventoryReport.java
public final class InventoryReport extends AbstractReport {
private final int itemCount;
public InventoryReport(String title, int itemCount) {
super(title);
if (itemCount < 0) {
throw new IllegalArgumentException("negative count");
}
this.itemCount = itemCount;
}
@Override
protected String body() {
return "items: " + itemCount;
}
}
این نمونه نشان میدهد کلاس انتزاعی میتواند سازنده و متد کامل داشته باشد. متد انتزاعی فقط بخش متغیر را واگذار میکند. اگر به مرور تعداد قلابها و استثناهای زیرکلاس زیاد شود، احتمالاً یک الگوریتم مشترک واقعی ندارید و باید ترکیب رفتارها را بررسی کنید. کلاس پایهای که مرتب برای یک زیرکلاس شرط خاص میگیرد، ممکن است به جای اشتراک، وابستگی شکننده ایجاد کرده باشد.
متد پیشفرض یک پیادهسازی نمونه در قرارداد ارائه میدهد و پیادهساز میتواند آن را بازنویسی کند. متد ایستا به خود اینترفیس تعلق دارد و بهصورت متد نمونه به کلاس پیادهساز ارث نمیرسد. متد خصوصی برای جزئیات مشترک داخل بدنههای اینترفیس مفید است و بخشی از قرارداد عمومی پیادهساز نیست. در نتیجه «پیادهسازی مشترک» بهتنهایی دلیل کافی برای انتخاب کلاس انتزاعی نیست.
// Labelled.java
public interface Labelled {
String label();
default String normalizedLabel() {
return normalize(label());
}
private static String normalize(String text) {
return java.util.Objects.requireNonNull(text).strip();
}
static boolean isBlank(String text) {
return text == null || text.isBlank();
}
}
در این نمونه، فراخوانی متد ایستا با نام اینترفیس انجام میشود. برای تفاوت متدهای وابسته به نوع و نمونه، مقایسه متد ایستا و متد نمونه را بخوانید. متد پیشفرض هم وضعیت نمونه تازهای در اینترفیس ایجاد نمیکند؛ اگر به داده نیاز دارد، باید از قرارداد رفتاری استفاده کند. چنین متدی نباید به فرضهای پنهان درباره داخل همه کلاسهای پیادهساز متکی باشد.
متد پیشفرض به تکامل قرارداد کمک میکند، اما تضمین نمیکند هر تغییر برای هر مصرفکننده بدون مسئله باشد. ممکن است دو قرارداد مستقل، متد پیشفرض همنام و ناسازگار داشته باشند یا تغییر رفتار، انتظار مصرفکننده را بشکند. سازگاری کد و سازگاری معنایی را جدا بررسی کنید. برای کتابخانه عمومی، آزمون مصرفکنندگان و یادداشت تغییرات همچنان ضروری است؛ وجود یک بدنه پیشفرض جادوی حذف ریسک نیست.
اگر دو اینترفیس نامرتبط، متد پیشفرض یکسانی ارائه کنند، کلاس باید تعارض را صریحاً حل کند. نمونه زیر یکی از پیادهسازیهای مستقیم را انتخاب میکند؛ همچنین میتوانید منطق جدیدی بنویسید. این انتخاب باید از نظر معنایی با هر دو قرارداد سازگار باشد، نه صرفاً راهی برای ساکت کردن کامپایلر. نام دقیق قراردادها و انتظار رفتاریشان در پروژه اهمیت بیشتری از کوتاهی کد دارد.
// LeftLabel.java
public interface LeftLabel {
default String label() { return "left"; }
}
// RightLabel.java
public interface RightLabel {
default String label() { return "right"; }
}
// CombinedLabel.java
public final class CombinedLabel implements LeftLabel, RightLabel {
@Override
public String label() {
return LeftLabel.super.label();
}
}
قاعده کلی دیگر این است که پیادهسازی معتبر کلاس در سلسلهمراتب، نسبت به پیشفرض اینترفیس اولویت دارد و اینترفیس اختصاصیتر میتواند رفتار ارثرسیده را دقیقتر کند. حالتهای پیچیده، بهویژه بازاعلام متد انتزاعی در کلاس پایه، نیازمند مراجعه به قواعد دقیق زباناند. خلاصههای شبکههای اجتماعی را جای مشخصات زبان نگذارید و برای هر ابهام، یک مثال کوچک قابل کامپایل در نسخه هدف بسازید.

گاهی مسئله واقعی، تغییر مستقل دو رفتار است؛ مثلاً قالب گزارش و مقصد خروجی. اگر برای هر ترکیب، زیرکلاس تازه بسازید، سلسلهمراتب سریع پیچیده میشود. میتوانید یک شیء گزارشساز داشته باشید که قالبساز و خروجینویس را از سازنده دریافت میکند. این همان ترکیب رفتارهاست: به جای آنکه همه تغییرها را در زنجیره وراثت جاسازی کنید، آنها را به همکارهای کوچک واگذار میکنید.
برای انتخاب، از خودتان بپرسید کدام بخش باید مستقل تغییر کند. اگر مقصد خروجی عوض میشود ولی منطق گزارش ثابت است، درگاه خروجی مفید است. اگر چند گزارش واقعاً ترتیب ثابت و داده پایه مشترک دارند، کلاس انتزاعی میتواند منطقی باشد. ترکیب و وراثت دشمن هم نیستند؛ حتی یک کلاس انتزاعی ممکن است رفتار فرعی را به یک اینترفیس تزریقشده واگذار کند. اصل، وضوح قرارداد و هزینه تغییر است.
وجود فیلد مشترک، مجوز عمومی کردن آن نیست. فیلد خصوصی با رفتار کنترلشده معمولاً مرز مطمئنتری از وضعیت محافظتشدهای دارد که هر زیرکلاس آزادانه تغییرش میدهد. مرور انواع داده در جاوا و کلاس رشته در جاوا کمک میکند تفاوت مقدار، مرجع و تغییرپذیری را دقیقتر ببینید. در طراحی قرارداد، مشخص کنید آیا مصرفکننده اجازه نگهداری یا تغییر داده دریافتی را دارد.
برای نمونه، ساخت رشته خروجی یک موضوع متفاوت از انتخاب رابطه نوعهاست. اگر در حلقه به ساخت متن نیاز دارید، مقایسه String، StringBuilder و StringBuffer راهنمای مناسبتری است. از این جزئیات اجرایی نتیجه نگیرید که اینترفیس ذاتاً سریعتر یا کلاس انتزاعی ذاتاً کندتر است. کارایی به مسیر واقعی اجرا، پیادهسازی و بهینهسازی ماشین مجازی وابسته است و بدون اندازهگیری نمیتوان حکم عمومی داد.
قابلیت انواع بسته از جاوا ۱۷ نهایی شده است و در نسخههای هدف این مقاله قابل استفاده است. با آن میتوانید زیرنوعهای مستقیم مجاز را کنترل کنید. این ویژگی نه جای اینترفیس را میگیرد و نه کلاس انتزاعی را حذف میکند؛ هر دو میتوانند در یک خانواده کنترلشده نقش داشته باشند. مقررات بسته و ماژول و الزامهای زیرنوع مستقیم را از مستندات رسمی بررسی کنید.
// Outcome.java
public sealed interface Outcome permits Success, Failure {
}
// Success.java
public record Success(String value) implements Outcome {
}
// Failure.java
public record Failure(String message) implements Outcome {
}
در این چیدمان نمونه، فایلها در یک بسته و ماژول سازگار قرار میگیرند. رکوردها نهاییاند و برای نمایش حاملهای داده کوچک استفاده شدهاند. اگر زیرنوع معمولی تعریف کنید، باید وضعیت گسترشش را مطابق قواعد sealed، نهایی یا غیربسته تعیین کنید. خانواده بسته وقتی مناسب است که واقعاً مجموعه حالتها را کنترل میکنید؛ برای افزونههای آزاد کاربران کتابخانه، ممکن است خلاف هدف انعطاف باشد.

اگر میخواهید قابلیت مشترک میان انواع نامرتبط تعریف کنید، از قرارداد کوچک شروع کنید. اگر وضعیت نمونه، سازنده و اسکلت الگوریتم مشترک دارید، کلاس انتزاعی را بررسی کنید. اگر تغییرها مستقلاند، ترکیب را جدی بگیرید. برای API عمومی، سطح قرارداد را کوچک نگه دارید؛ هر متدی که منتشر میکنید تعهد نگهداری است. تصمیم را با یک سناریوی تغییر واقعی آزمایش کنید، نه با حفظ کردن یک جمله مطلق.
از این خطاها دوری کنید: اینترفیس پر از ثابتهای نامرتبط، کلاس پایهای که همه خدمات را میشناسد، متد پیشفرضی که داده پنهان لازم دارد و زیرکلاسی که بیشتر متدهای پایه را غیرفعال میکند. همچنین بازنویسی را با بارگذاری متدها اشتباه نگیرید. یک قرارداد باید رفتار معتبر را روشن کند؛ صرف یکسان بودن امضای متدها، تضمین نمیکند دو پیادهسازی از نظر کسبوکار قابل جایگزینی باشند.
بله؛ در جاوای امروزی متد پیشفرض، ایستا و خصوصی با بدنه ممکن است. بااینحال اینترفیس سازنده یا وضعیت نمونه ندارد. برای تشخیص ابزار مناسب، فقط به داشتن یا نداشتن بدنه نگاه نکنید؛ رابطه نوعها و نیاز به داده مشترک مهمتر است.
یک کلاس جاوا فقط یک ابرکلاس مستقیم دارد، چه آن ابرکلاس انتزاعی باشد چه نباشد. در مقابل میتواند چند اینترفیس را پیادهسازی کند. اگر چند رفتار مستقل نیاز دارید، ترکیب اشیا و قراردادهای کوچک را بررسی کنید.
متد پیشفرض بدنه رفتاری ارائه میکند و قابل بازنویسی است؛ متد انتزاعی پیادهسازی لازم را به مسیر زیرنوعها واگذار میکند. در تعارض چند پیشفرض نامرتبط، کلاس باید انتخاب یا پیادهسازی روشن بدهد. برای کتابخانه، تغییر معنای رفتار را نیز بررسی کنید.
پاسخ مطلق ندارد. قرارداد کوچک برای انعطاف پیادهسازها مناسب است؛ کلاس پایه میتواند اجرای مشترک اختیاری بدهد. برخی کتابخانهها هر دو را عرضه میکنند. هزینه سازگاری آینده، نیاز به وضعیت نمونه و آزادی گسترش را با هم بسنجید.
فقط گسترش خانواده را کنترل میکند و تفاوتهای اصلی را حذف نمیکند. اینترفیس بسته همچنان سازنده یا فیلد نمونه ندارد. آن را برای مدل کردن مجموعه زیرنوعهای کنترلشده انتخاب کنید، نه برای دور زدن تفاوتهای معمول زبان.
سازنده و فیلد نمونه ندارد. فیلدهای اعلامشده در آن عمومی، ایستا و نهاییاند و معمولاً برای ثابت مرتبط با قرارداد مناسباند. اگر نیاز واقعی شما مقداردهی وضعیت اختصاصی هر شیء است، آن داده را در کلاس پیادهساز یا کلاس پایه مناسب قرار دهید. استفاده از مجموعه قابل تغییر عمومی برای شبیهسازی وضعیت نمونه، مرز طراحی را مخدوش میکند و آزمونهای مستقل را هم ممکن است به یکدیگر وابسته کند.
بگویید اینترفیس بیشتر قرارداد قابلیت است و کلاس انتزاعی میتواند پایه مشترک با وضعیت و سازنده باشد. بعد محدودیت یک ابرکلاس و امکان چند اینترفیس را توضیح دهید. جمله را با یک مثال واقعی و اشاره به متد پیشفرض کامل کنید. پاسخ خوب به نسخه زبان حساس است؛ تکرار جمله قدیمی «اینترفیس هرگز پیادهسازی ندارد» نشان میدهد به تحول زبان توجه نکردهاید. نیازی هم به ادعای سرعت بیشتر یکی بدون آزمون ندارید.
خیر؛ اشتراک بدنه فقط یکی از نیازهاست. اگر وضعیت هر نمونه، مقداردهی اولیه و نگه داشتن ترتیب مراحل مشترک لازم دارید، کلاس انتزاعی همچنان ممکن است مناسب باشد. از طرف دیگر، اگر فقط یک رفتار مشتقشده از قرارداد عمومی میخواهید، متد پیشفرض میتواند کافی باشد. معیار را از نیاز طراحی بگیرید، نه از این تصور که قابلیت تازه زبان باید همیشه ابزار قدیمی را کنار بزند. برای پیادهسازی پروژه با مرز روشن، خدمات توسعه وبسایت رادینو را با سناریوی تغییر و آزمون پذیرش مشخص بررسی کنید.
نظرات 0
برای ارسال نظر وارد حساب کاربری شوید.
هنوز نظری ثبت نشده؛ اولین نفر باشید.