Kapan metode pribadi mengambil rute publik untuk mengakses data pribadi?

11

Kapan metode pribadi mengambil rute publik untuk mengakses data pribadi? Misalnya, jika saya memiliki kelas 'pengali' yang tidak dapat diubah ini (sedikit dibuat-buat, saya tahu):

class Multiplier {
public:
    Multiplier(int a, int b) : a(a), b(b) { }
    int getA() const { return a; }
    int getB() const { return b; }
    int getProduct() const { /* ??? */ }
private:
    int a, b;
};

Ada dua cara yang bisa saya terapkan getProduct:

    int getProduct() const { return a * b; }

atau

    int getProduct() const { return getA() * getB(); }

Karena maksudnya di sini adalah menggunakan nilai a, yaitu untuk mendapatkan a , menggunakan getA()untuk mengimplementasikan getProduct()tampaknya lebih bersih bagi saya. Saya lebih suka menghindari penggunaan akecuali saya harus memodifikasinya. Kekhawatiran saya adalah bahwa saya tidak sering melihat kode ditulis seperti ini, dalam pengalaman saya a * bakan menjadi implementasi yang lebih umum daripada getA() * getB().

Haruskah metode pribadi menggunakan cara publik ketika mereka dapat mengakses sesuatu secara langsung?

0x5f3759df
sumber

Jawaban:

7

Itu tergantung dari arti sebenarnya dari a, bdan getProduct.

Tujuan getter adalah untuk dapat mengubah implementasi aktual sambil menjaga antarmuka objek tetap sama. Misalnya, jika suatu hari, getAmenjadi return a + 1;, perubahan dilokalkan ke pengambil.

Kasus skenario nyata terkadang lebih rumit daripada bidang dukungan konstan yang ditetapkan melalui konstruktor yang terkait dengan pengambil. Misalnya, nilai bidang dapat dihitung atau dimuat dari database dalam versi asli kode. Di versi berikutnya, caching dapat ditambahkan untuk mengoptimalkan kinerja. Jika getProductterus menggunakan versi yang dihitung, itu tidak akan mendapat manfaat dari caching (atau pengelola akan melakukan perubahan yang sama dua kali).

Jika masuk akal untuk getProductdigunakan adan bsecara langsung, gunakan itu. Kalau tidak, gunakan getter untuk mencegah masalah pemeliharaan nanti.

Contoh di mana seseorang akan menggunakan getter:

class Product {
public:
    Product(ProductId id) : {
        price = Money.fromCents(
            data.findProductById(id).price,
            environment.currentCurrency
        )
    }

    Money getPrice() {
        return price;
    }

    Money getPriceWithRebate() {
        return getPrice().applyRebate(rebate); // ← Using a getter instead of a field.
    }
private:
    Money price;
}

Sementara untuk saat ini, pengambil tidak mengandung logika bisnis, tidak dikecualikan bahwa logika dalam konstruktor akan dimigrasikan ke pengambil untuk menghindari melakukan pekerjaan basis data saat menginisialisasi objek:

class Product {
public:
    Product(ProductId id) : id(id) { }

    Money getPrice() {
        return Money.fromCents(
            data.findProductById(id).price,
            environment.currentCurrency
        )
    }

    Money getPriceWithRebate() {
        return getPrice().applyRebate(rebate);
    }
private:
    const ProductId id;
}

Kemudian, caching dapat ditambahkan (dalam C #, orang akan menggunakan Lazy<T>, membuat kode pendek dan mudah; Saya tidak tahu apakah ada yang setara dalam C ++):

class Product {
public:
    Product(ProductId id) : id(id) { }

    Money getPrice() {
        if (priceCache == NULL) {
            priceCache = Money.fromCents(
                data.findProductById(id).price,
                environment.currentCurrency
            )

        return priceCache;
    }

    Money getPriceWithRebate() {
        return getPrice().applyRebate(rebate);
    }
private:
    const ProductId id;
    Money priceCache;
}

Kedua perubahan difokuskan pada pengambil dan bidang dukungan, kode yang tersisa tidak terpengaruh. Jika, sebagai gantinya, saya telah menggunakan bidang alih-alih pengambil getPriceWithRebate, saya harus mencerminkan perubahan di sana juga.

Contoh di mana seseorang mungkin akan menggunakan bidang pribadi:

class Product {
public:
    Product(ProductId id) : id(id) { }
    ProductId getId() const { return id; }
    Money getPrice() {
        return Money.fromCents(
            data.findProductById(id).price, // ← Accessing `id` directly.
            environment.currentCurrency
        )
    }
private:
    const ProductId id;
}

Pencari mudah: itu adalah representasi langsung dari bidang konstan (mirip dengan C # 's readonly) yang tidak diharapkan untuk berubah di masa depan: kemungkinan, ID pengambil tidak akan pernah menjadi nilai yang dihitung. Jadi sederhanakan, dan akses bidang secara langsung.

Manfaat lain adalah bahwa getIdmungkin dihapus di masa depan jika tampaknya tidak digunakan di luar (seperti pada potongan kode sebelumnya).

Arseni Mourzenko
sumber
Saya tidak dapat memberi Anda +1 karena contoh Anda untuk menggunakan bidang pribadi bukan merupakan IMHO, terutama karena Anda telah mendeklarasikan const: Saya berasumsi itu berarti kompiler akan menyambungkan getIdpanggilan tetap dan memungkinkan Anda untuk membuat perubahan di kedua arah. (Kalau tidak, saya sepenuhnya setuju dengan alasan Anda untuk menggunakan getter.) Dan dalam bahasa yang menyediakan sintaksis properti, ada lebih sedikit alasan untuk tidak menggunakan properti daripada bidang dukungan secara langsung.
Mark Hurd
1

Biasanya, Anda akan menggunakan variabel secara langsung. Anda berharap untuk mengubah semua anggota saat mengubah implementasi kelas. Tidak menggunakan variabel secara langsung hanya membuatnya lebih sulit untuk secara benar mengisolasi kode yang bergantung pada mereka dan membuatnya lebih sulit untuk membaca anggota.

Ini tentu saja berbeda jika pengambil menerapkan logika nyata, dalam hal itu tergantung pada apakah Anda perlu memanfaatkan logika mereka atau tidak.

DeadMG
sumber
1

Saya akan mengatakan bahwa menggunakan metode publik akan lebih disukai, jika bukan karena alasan lain selain untuk menyesuaikan dengan KERING .

Saya tahu dalam kasus Anda, Anda memiliki bidang dukungan sederhana untuk pengakses Anda, tetapi Anda bisa memiliki logika tertentu, misalnya kode pemuatan malas, yang harus Anda jalankan sebelum pertama kali Anda menggunakan variabel itu. Dengan demikian, Anda ingin memanggil pengakses Anda alih-alih merujuk langsung ke bidang Anda. Meskipun Anda tidak memiliki ini dalam kasus ini, masuk akal untuk tetap pada satu konvensi. Dengan begitu, jika Anda pernah membuat perubahan pada logika Anda, Anda hanya perlu mengubahnya di satu tempat.

rory.ap
sumber
0

Untuk kelas, kesederhanaan kecil ini menang. Saya hanya akan menggunakan a * b.

Untuk sesuatu yang jauh lebih rumit, saya akan sangat mempertimbangkan menggunakan getA () * getB () jika saya ingin memisahkan dengan jelas antarmuka "minimal" dari semua fungsi lain di API publik lengkap. Contoh yang bagus adalah std :: string dalam C ++. Ini memiliki 103 fungsi anggota, tetapi hanya 32 di antaranya yang benar - benar membutuhkan akses ke anggota pribadi. Jika Anda memiliki kelas yang kompleks, memaksa semua fungsi "non-inti" untuk secara konsisten melalui "API inti" dapat membuat implementasi jauh lebih mudah untuk diuji, debug, dan refactor.

Ixrec
sumber
1
Jika Anda memiliki kelas yang kompleks, Anda harus dipaksa untuk memperbaikinya, bukan dengan bantuan band.
DeadMG
Sepakat. Saya mungkin harus memilih contoh dengan hanya 20-30 fungsi.
Ixrec
1
"103 fungsi" adalah herring merah. Metode kelebihan beban harus dihitung sekali, dalam hal kerumitan antarmuka.
Avner Shahar-Kashtan
Saya sepenuhnya tidak setuju. Kelebihan beban yang berbeda dapat memiliki semantik dan antarmuka yang berbeda pula.
DeadMG
Bahkan untuk contoh "kecil" ini, getA() * getB()lebih baik dalam jangka menengah dan panjang.
Mark Hurd