Berjuang dengan Prinsip Tanggung Jawab Tunggal

11

Pertimbangkan contoh ini:

Saya punya situs web. Itu memungkinkan pengguna untuk membuat posting (bisa apa saja) dan menambahkan tag yang menggambarkan posting. Dalam kode, saya memiliki dua kelas yang mewakili pos dan tag. Mari kita panggil kelas-kelas ini Postdan Tag.

Postmengurus pembuatan posting, menghapus posting, memperbarui posting, dll. Tagmengurus pembuatan tag, menghapus tag, memperbarui tag, dll.

Ada satu operasi yang hilang. Menautkan tag ke posting. Saya berjuang dengan siapa yang harus melakukan operasi ini. Itu bisa cocok dengan baik di kedua kelas.

Di satu sisi, Postkelas bisa memiliki fungsi yang mengambil Tagparameter, dan kemudian menyimpannya dalam daftar tag. Di sisi lain, Tagkelas bisa memiliki fungsi yang mengambil Postsebagai parameter dan menautkan Tagke Post.

Di atas hanyalah contoh dari masalah saya. Saya benar-benar mengalami ini dengan beberapa kelas yang semuanya mirip. Ini bisa cocok dengan baik di keduanya. Singkat untuk tidak menempatkan fungsionalitas di kedua kelas, konvensi atau gaya desain apa yang ada untuk membantu saya memecahkan masalah ini. Saya berasumsi harus ada sesuatu yang kurang dari hanya memilih satu?

Mungkin meletakkannya di kedua kelas adalah jawaban yang benar?

AngryBird
sumber

Jawaban:

11

Seperti Kode Bajak Laut, SRP lebih merupakan pedoman daripada aturan, dan itu bahkan bukan kata yang sangat baik. Sebagian besar pengembang telah menerima redefinisi Martin Fowler (dalam Refactoring ) dan Robert Martin (dalam Clean Code ), menunjukkan bahwa suatu kelas seharusnya hanya memiliki satu alasan untuk berubah (sebagai lawan dari satu tanggung jawab).

Ini adalah pedoman yang bagus, solid (permisi pun), tetapi hampir sama bahayanya untuk menutup telepon seperti halnya mengabaikannya.

Jika Anda dapat menambahkan Posting ke Tag dan sebaliknya, Anda belum melanggar prinsip tanggung jawab tunggal. Keduanya masih memiliki satu alasan untuk berubah - jika struktur objek itu berubah. Mengubah struktur yang satu tidak mengubah cara ditambahkan ke yang lain, jadi Anda tidak menambahkan "tanggung jawab" baru padanya.

Keputusan akhir Anda harus benar-benar ditentukan oleh fungsi yang diperlukan di ujung depan. Mungkin ada kebutuhan untuk menambahkan Tag ke Posting di beberapa titik, jadi lakukan sesuatu seperti berikut:

// C-style-language pseudo-code
class Post {
    string _title;
    string _content;
    Date _date;
    List<Tag> _tags;

    Post(string title, string content) {
        _title = title;
        _content = content;
        _date = Now;
        _tags = new List<Tag>();
    }

    Tag[] getTags() {
        return _tags.toArray();
    }

    void addTag(Tag tag) {
        if (_tags.contains(tag)) {
            throw "Cannot add tag twice";
        }

        _tags.Add(tag);
        tag.referencePost(this);
    }

    // more stuff here, obviously
}

class Tag {
    string _name;
    List<Post> _posts;

    Tag(string name) {
        _name = name;
    }

    Post[] getPosts() {
        return _posts.toArray();
    }

    void referencePost(Post post) {
        if (!post.getTags().contains(this) || _posts.contains(post)) {
            throw "Only reference a post by calling Post.addTag()";
        }

        _posts.Add(post);
    }

    // more stuff here too
}

Jika nanti Anda menemukan kebutuhan untuk menambahkan Posting ke Tag, cukup tambahkan metode addPost ke kelas Tag dan metode referenceTag ke kelas Post. Jelas, saya telah menamai mereka secara berbeda sehingga Anda tidak sengaja menyebabkan stack overflow dengan memanggil addTag dari addPost dan addPost dari addTag.

pdr
sumber
Saya pikir hubungan antara Tag dan Posting adalah banyak-ke-banyak, dalam hal ini masuk akal bagi Tag untuk menyimpan referensi ke beberapa Posting. Bagaimana Anda menangani ini jika Anda menyimpan satu referensi?
Andres F.
@AndresF .: Saya setuju dengan Anda, jadi saya jelas tidak menuliskan jawaban saya dengan baik. Saya telah mengedit secara signifikan. (Maaf ke upvoter sebelumnya jika perubahan ini artinya saat Anda melihatnya.)
pdr
6

Tidak, tidak di keduanya! Itu harus di satu tempat.

Apa yang saya temukan tanpa henti dalam pertanyaan Anda adalah kenyataan bahwa Anda mengatakan " Postberhati-hati dalam membuat posting, menghapus posting, memperbarui posting" dan sama untuk Tag. Ya, itu tidak benar. Posthanya dapat mengurus pembaruan, sama untuk Tag. Membuat dan menghapus adalah pekerjaan orang lain, eksternal Postdan Tag(sebut saja Store).

Tanggung jawab yang baik untuk Postadalah "tahu pembuatnya, konten dan tanggal pembaruan terakhir". Tanggung jawab yang baik Tagadalah "tahu nama dan tujuannya (baca: deskripsi)". Tanggung jawab yang baik untuk Storeadalah "tahu semua Posting dan semua Tag dan dapat menambah, menghapus dan mencari mereka".

Jika Anda melihat ketiga peserta ini, siapa yang paling alami yang harus memiliki pengetahuan tentang hubungan Post-Tag?

(bagi saya, itu adalah Post, sepertinya wajar "tahu tag-nya"; pencarian terbalik (semua posting untuk sebuah tag) tampaknya merupakan pekerjaan Toko; meskipun saya mungkin salah)

herba
sumber
Jika setiap posting memiliki daftar tag, dan / atau setiap tag memiliki daftar posting yang menyertakannya, orang dapat dengan mudah menjawab pertanyaan "apakah posting x termasuk tag y". Apakah ada cara untuk secara efisien menjawab pertanyaan seperti itu tanpa salah satu kelas mengambil tanggung jawab, selain dengan menggunakan sesuatu seperti ConditionalWeakTable(dengan asumsi orang cukup beruntung memiliki kerangka kerja di mana ada)?
supercat
3

Ada detail penting yang hilang dari persamaan. Mengapa tag mengandung post dan sebaliknya? Jawaban untuk pertanyaan ini menentukan solusi untuk setiap set yang diberikan.

Secara umum saya dapat memikirkan situasi yang serupa. Sebuah kotak dan isinya. Sebuah kotak memiliki konten sehingga hubungan has-a sesuai. Bisakah isi kotak? Tentu, sebuah kotak dengan sebuah kotak. Sebuah kotak isinya. Tapi IS-A bukan desain yang bagus untuk semua kotak. Dalam kasus seperti itu saya akan mempertimbangkan pola dekorator. Dengan cara ini sebuah kotak didekorasi dengan konten saat runtime sebagaimana diperlukan.

Tag juga dapat memiliki Posting, tetapi bagi saya ini bukan hubungan yang statis. Sebaliknya itu bisa berupa laporan dari semua posting yang memiliki tag kata. Dalam hal ini entitas baru, bukan has-a.

P.Brian.Mackey
sumber
2

Sementara dalam teori hal-hal seperti itu bisa berjalan baik, dalam praktiknya ketika Anda turun ke implementasi satu cara hampir selalu lebih cocok daripada yang lain. Firasat saya adalah itu akan lebih cocok di Postkelas karena asosiasi akan dibuat selama pembuatan posting atau pengeditan, ketika hal-hal lain tentang posting berubah pada saat yang sama.

Juga, jika Anda mengaitkan beberapa tag dan ingin melakukannya dalam satu pembaruan basis data, Anda harus membuat semacam daftar semua tag yang terkait dengan pos yang sama sebelum melakukan pembaruan. Daftar itu jauh lebih cocok di Postkelas.

Karl Bielefeldt
sumber
1

Secara pribadi, saya tidak akan menambahkan fungsi itu ke salah satu dari mereka.

Bagi saya, keduanya Postdan Tagmerupakan objek data, jadi seharusnya tidak menangani fungsionalitas basis data. Mereka seharusnya ada. Mereka dimaksudkan untuk menyimpan data, dan digunakan oleh bagian lain dari aplikasi Anda.

Sebagai gantinya, saya memiliki kelas lain yang bertanggung jawab untuk logika bisnis dan data Anda yang berkaitan dengan halaman web Anda. Jika halaman Anda menampilkan posting dan memungkinkan pengguna untuk menambahkan tag, maka kelas akan memiliki Postobjek, dan berisi fungsionalitas untuk ditambahkan Tagske sana Post. Jika halaman Anda menampilkan tag dan memungkinkan pengguna untuk menambahkan posting ke tag itu, itu akan berisi Tagobjek dan memiliki fungsi untuk ditambahkan Postske sana Tag.

Tapi itu hanya aku. Jika Anda merasa bahwa Anda harus menangani fungsionalitas basis data dalam objek data Anda, maka saya akan merekomendasikan jawaban pdr

Rachel
sumber
0

Ketika saya membaca pertanyaan ini, hal pertama yang muncul di benak saya adalah hubungan database Many To Many . Posting dapat memiliki banyak Tag ... Tag dapat memiliki banyak Posting .... Tampaknya bagi saya bahwa kedua kelas memerlukan kemampuan untuk mengelola hubungan ini sampai batas tertentu.

Dari sudut pandang Posting ...
Jika Anda mengedit atau membuat Posting, aktivitas sekunder menjadi mengelola hubungan Tag .

  1. Tambahkan Tag yang Ada ke Posting
  2. Cabut Tag dari Posting

IMO, pembuatan TAG yang sepenuhnya baru bukan milik di sini.

Dari sudut pandang Tag ...
Anda dapat membuat Tag tanpa harus menetapkannya ke Pos. Satu-satunya aktivitas yang saya lihat yang melibatkan interaksi dengan Post adalah fungsi Delete Tag. Namun, fungsi ini harus merupakan fungsi mandiri yang berdiri sendiri.

Ini hanya akan berfungsi jika ada tabel yang menghubungkan database yang menyelesaikan hubungan Many to Many

Michael Riley - AKA Gunny
sumber