سؤال

أقوم بإنشاء ORM صغير لبرنامج Java الذي أكتبه ...هناك فئة لكل جدول في قاعدة البيانات الخاصة بي، وكلها وراثة من ModelBase.

ModelBase هو مجردة ويوفر مجموعة من الأساليب الثابتة للعثور على الكائنات وربطها من قاعدة البيانات، على سبيل المثال:

public static ArrayList findAll(Class cast_to_class) {
  //build the sql query & execute it 
}

لذلك يمكنك أن تفعل أشياء مثل ModelBase.findAll(Albums.class) للحصول على قائمة بجميع الألبومات المستمرة.مشكلتي هي أنه في هذا السياق الثابت، أحتاج إلى الحصول على سلسلة SQL المناسبة من ألبوم الفصل المحدد.لا أستطيع الحصول على طريقة ثابتة مثل

public class Album extends ModelBase {
  public static String getSelectSQL() { return "select * from albums.....";}
}

لأنه لا يوجد تعدد الأشكال للطرق الثابتة في Java.لكنني لا أريد أن أفعل getSelectSQL() طريقة مثيل في Album لأنني سأحتاج بعد ذلك إلى إنشاء مثيل له فقط للحصول على سلسلة ثابتة في السلوك.

في اللحظة، findAll() يستخدم الانعكاس للحصول على SQL المناسب للفئة المعنية:

select_sql = (String)cast_to_class.getDeclaredMethod("getSelectSql", new Class[]{} ).invoke(null, null);

ولكن هذا الإجمالي جدا.

إذن أي أفكار؟إنها مشكلة عامة أواجهها مرارًا وتكرارًا - عدم القدرة على تحديد طرق ثابتة مجردة في الفئات أو الواجهات.أنا أعرف لماذا تعدد الأشكال بالطريقة الثابتة لا يعمل ولا يمكنه العمل، لكن هذا لا يمنعني من الرغبة في استخدامه مرة أخرى!

هل هناك أي نمط/بنية تسمح لي بالتأكد من أن الفئات الفرعية المحددة X و Y تنفذ طريقة فئة (أو في حالة الفشل في ذلك، ثابت فئة!)؟

هل كانت مفيدة؟

المحلول

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

بدءًا من تعليقك "أحتاج إلى إنشاء مثيل له فقط للحصول على سلسلة ثابتة في السلوك"

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

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

class ModelBase {
    // Initialize the queries
    private static Map<String,String> selectMap = new HashMap<String,String>(); static {
        selectMap.put( "Album", "select field_1, field_2 from album");
        selectMap.put( "Artist", "select field_1, field_2 from artist");
        selectMap.put( "Track", "select field_1, field_2 from track");
    }

    // Finds all the objects for the specified class...
    // Note: it is better to use "List" rather than "ArrayList" I'll explain this later.
    public static List findAll(Class classToFind ) {
        String sql = getSelectSQL( classToFind );
        results = execute( sql );
        //etc...
        return ....
    }

    // Return the correct select sql..
    private static String getSelectSQL( Class classToFind ){
        String statement = tableMap.get( classToFind.getSimpleName() );
        if( statement == null ) {
            throw new IllegalArgumentException("Class " + 
                 classToFind.getSimpleName + " is not mapped");
        }
        return statement;

    }
}

أي قم بتعيين جميع البيانات باستخدام خريطة.الخطوة التالية "الواضحة" لذلك هي تحميل الخريطة من مصدر خارجي، مثل ملف خصائص، أو ملف XML أو حتى (لماذا لا) جدول قاعدة بيانات، لمزيد من المرونة.

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

// Client usage:

...
List albums = ModelBase.findAll( Album.class );

...

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

// Second option, instance used under the hood.
class ModelBase {
    // Initialize the queries
    private static Map<String,ModelBase> daoMap = new HashMap<String,ModelBase>(); static {
        selectMap.put( "Album", new AlbumModel() );
        selectMap.put( "Artist", new ArtistModel());
        selectMap.put( "Track", new TrackModel());
    }

    // Finds all the objects for the specified class...
    // Note: it is better to use "List" rather than "ArrayList" I'll explain this later.
    public static List findAll(Class classToFind ) {
        String sql = getSelectSQL( classToFind );
        results = execute( sql );
        //etc...
        return ....
    }

    // Return the correct select sql..
    private static String getSelectSQL( Class classToFind ){
        ModelBase dao = tableMap.get( classToFind.getSimpleName() );
        if( statement == null ) {
            throw new IllegalArgumentException("Class " + 
                 classToFind.getSimpleName + " is not mapped");
        }
        return dao.selectSql();
    }
    // Instance class to be overrided... 
    // this is "protected" ... 
    protected abstract String selectSql();
}
class AlbumModel  extends ModelBase {
    public String selectSql(){
        return "select ... from album";
    }
}
class ArtistModel  extends ModelBase {
    public String selectSql(){
        return "select ... from artist";
    }
}
class TrackModel  extends ModelBase {
    public String selectSql(){
        return "select ... from track";
    }
}

ولا تحتاج إلى تغيير رمز العميل، ولا يزال لديك قوة تعدد الأشكال.

// Client usage:

...
List albums = ModelBase.findAll( Album.class ); // Does not know , behind the scenes you use instances.

...

آمل أن يساعد هذا.

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

نصائح أخرى

ثابت هو الشيء الخطأ الذي يجب استخدامه هنا.

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

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

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

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

لماذا لا تستخدم التعليقات التوضيحية؟إنهم يتناسبون جيدًا مع ما تفعله:لإضافة معلومات تعريفية (هنا استعلام SQL) إلى الفصل الدراسي.

كما هو مقترح، يمكنك استخدام التعليقات التوضيحية، أو يمكنك نقل الأساليب الثابتة إلى كائنات المصنع:

public abstract class BaseFactory<E> {
    public abstract String getSelectSQL();
    public List<E> findAll(Class<E> clazz) {
       // Use getSelectSQL();
    }
}

public class AlbumFactory extends BaseFactory<Album> {
    public String getSelectSQL() { return "select * from albums....."; }
}

لكن ليس من الرائحة الطيبة أن يكون لديك أشياء بدون أي حالة.

إذا كنت تقوم بتمرير فئة إلى findAll، فلماذا لا يمكنك تمرير فئة إلى getSelectSQL في ModelBase؟

النجمي:هل تقصد أن getSelectSQL موجود فقط في ModelBase، ويستخدم ما تم تمريره في الفصل لإنشاء اسم جدول أو شيء من هذا القبيل؟لا أستطيع فعل ذلك، لأن بعض النماذج لها بنيات محددة مختلفة جدًا، لذلك لا أستطيع استخدام "select * from" + classToTableName();.وأي محاولة للحصول على معلومات من النماذج حول بنائها المحدد ستواجه نفس المشكلة من السؤال الأصلي - فأنت بحاجة إلى مثال للنموذج أو بعض التأملات الرائعة.

الأداة:بالتأكيد سألقي نظرة على التعليقات التوضيحية.على الرغم من أنني لا أستطيع إلا أن أتساءل ماذا فعل الناس بهذه المشاكل قبل أن يكون هناك تفكير؟

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

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

إجابة قصيرة (Java أو .NET):لا يمكنك.إجابة أطول - يمكنك ذلك إذا كنت لا تمانع في استخدام تعليق توضيحي (انعكاس) على مستوى الفصل أو إنشاء كائن (طريقة المثيل) ولكن لا يعد أي منهما "نظيفًا" حقًا.

راجع سؤالي السابق (المتعلق) هنا: كيفية التعامل مع الحقول الثابتة التي تختلف حسب فئة التنفيذاعتقدت أن الإجابات كانت كلها واهية حقًا وأخطأت الهدف.سؤالك صياغة أفضل بكثير.

وأنا أتفق مع جيزمو:أنت إما تنظر إلى التعليقات التوضيحية أو إلى نوع ما من ملفات التكوين.سألقي نظرة على Hibernate وأطر ORM الأخرى (وربما حتى المكتبات مثل log4j!) لمعرفة كيفية تعاملهم مع تحميل المعلومات التعريفية على مستوى الفصل الدراسي.

لا يمكن أو ينبغي القيام بكل شيء برمجيًا، أشعر أن هذه قد تكون إحدى تلك الحالات.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top