Membaca melalui artikel pedas tentang kelemahan OOP yang mendukung beberapa paradigma lain saya telah menemukan contoh bahwa saya tidak dapat menemukan terlalu banyak kesalahan.
Saya ingin terbuka dengan argumen penulis, dan meskipun saya secara teoritis dapat memahami poin mereka, satu contoh khususnya saya mengalami kesulitan mencoba membayangkan bagaimana itu akan lebih baik diimplementasikan dalam, katakanlah, bahasa FP.
// Consider the case where “SimpleProductManager” is a child of
// “ProductManager”:
public class SimpleProductManager implements ProductManager {
private List products;
public List getProducts() {
return products;
}
public void increasePrice(int percentage) {
if (products != null) {
for (Product product : products) {
double newPrice = product.getPrice().doubleValue() *
(100 + percentage)/100;
product.setPrice(newPrice);
}
}
}
public void setProducts(List products) {
this.products = products;
}
}
// There are 3 behaviors here:
getProducts()
increasePrice()
setProducts()
// Is there any rational reason why these 3 behaviors should be linked to
// the fact that in my data hierarchy I want “SimpleProductManager” to be
// a child of “ProductManager”? I can not think of any. I do not want the
// behavior of my code linked together with my definition of my data-type
// hierarchy, and yet in OOP I have no choice: all methods must go inside
// of a class, and the class declaration is also where I declare my
// data-type hierarchy:
public class SimpleProductManager implements ProductManager
// This is a disaster.
Perhatikan bahwa saya tidak mencari bantahan untuk atau melawan argumen penulis untuk "Apakah ada alasan rasional mengapa 3 perilaku ini harus dikaitkan dengan hierarki data?".
Apa yang secara khusus saya tanyakan adalah bagaimana contoh ini akan dimodelkan / diprogram dalam bahasa FP (Kode aktual, bukan secara teoritis)?
object-oriented
functional-programming
Danny Yaroslavski
sumber
sumber
Jawaban:
Dalam gaya FP,
Product
akan menjadi kelas yang tidak dapat diubah,product.setPrice
tidak akan bermutasiProduct
objek tetapi mengembalikan objek baru, danincreasePrice
fungsi akan menjadi fungsi "mandiri". Menggunakan sintaks yang mirip dengan milik Anda (mirip C # / Java), fungsi yang setara bisa terlihat seperti ini:Seperti yang Anda lihat, intinya tidak terlalu berbeda di sini, kecuali kode "boilerplate" dari contoh OOP yang dibuat dihilangkan. Namun, saya tidak melihat ini sebagai bukti bahwa OOP mengarah pada kode yang membengkak, hanya sebagai bukti untuk fakta jika seseorang membangun contoh kode yang cukup buatan, adalah mungkin untuk membuktikan apa pun.
sumber
null
mana koleksi adalah tipe pengembalian. / kata-kata kasarDalam bahasa FP "a"? Jika ada yang mencukupi, maka saya memilih Emacs lisp. Itu memang memiliki konsep jenis (jenis, semacam), tetapi hanya yang built-in. Jadi contoh Anda direduksi menjadi "bagaimana Anda mengalikan setiap item dalam daftar dengan sesuatu dan mengembalikan daftar baru".
Ini dia. Bahasa lain akan serupa, dengan perbedaan bahwa Anda mendapatkan manfaat dari jenis eksplisit dengan semantik "pencocokan" fungsional yang biasa. Lihat Haskell:
(Atau sesuatu seperti itu, sudah lama ...)
Mengapa? Saya mencoba membaca artikel; Saya harus menyerah setelah satu halaman dan dengan cepat memindai sisanya.
Masalah artikel ini bukan karena menentang OOP. Saya juga tidak membabi buta "pro OOP". Saya telah memprogram dengan paradigma logika, fungsional dan OOP, cukup sering dalam bahasa yang sama bila memungkinkan, dan sering tanpa salah satu dari ketiganya, murni imperatif atau bahkan pada tingkat assembler. Saya tidak akan pernah mengatakan bahwa salah satu dari paradigma itu sangat unggul bagi yang lain dalam setiap aspek. Apakah saya berpendapat bahwa saya lebih suka bahasa X daripada Y? Tentu saja saya akan! Tapi bukan itu yang dimaksud artikel itu.
Masalah dari artikel ini adalah ia menggunakan banyak alat retorika (fallacy) dari kalimat pertama hingga terakhir. Ini benar-benar sia-sia bahkan untuk mulai menggambarkan semua kesalahan yang dikandungnya. Penulis membuatnya sangat jelas bahwa ia tidak tertarik dalam diskusi, ia sedang dalam perang salib. Jadi mengapa repot?
Pada akhirnya, semua itu hanyalah alat untuk menyelesaikan pekerjaan. Mungkin ada pekerjaan di mana OOP lebih baik, dan mungkin ada pekerjaan lain di mana FP lebih baik, atau di mana keduanya berlebihan. Yang penting adalah memilih alat yang tepat untuk pekerjaan itu, dan menyelesaikannya.
sumber
Penulis membuat poin yang sangat baik kemudian memilih contoh yang kurang bagus untuk mencoba mendukungnya. Keluhan tidak dengan implementasi kelas, itu dengan gagasan bahwa hirarki data tidak terpisahkan dengan hirarki fungsi.
Oleh karena itu, untuk memahami maksud penulis, tidak akan membantu hanya melihat bagaimana ia akan mengimplementasikan kelas tunggal ini dalam gaya fungsional. Anda harus melihat bagaimana ia akan mendesain seluruh konteks data dan fungsi di sekitar kelas ini dalam gaya fungsional.
Pikirkan tentang tipe data potensial yang terlibat dalam produk dan harga. Untuk bertukar pikiran beberapa: nama, kode upc, kategori, berat pengiriman, harga, mata uang, kode diskon, aturan diskon.
Ini adalah bagian yang mudah dari desain berorientasi objek. Kami hanya membuat kelas untuk semua "objek" di atas dan kami bagus, kan? Buat
Product
kelas untuk menggabungkan beberapa dari mereka bersama?Tapi tunggu, Anda dapat memiliki koleksi dan agregat dari beberapa jenis: Set [kategori], (kode diskon -> harga), (jumlah -> jumlah diskon), dan sebagainya. Di mana mereka cocok? Apakah kita membuat yang terpisah
CategoryManager
untuk melacak semua jenis kategori yang berbeda, atau apakah tanggung jawab itu milikCategory
kelas yang sudah kita buat?Sekarang bagaimana dengan fungsi yang memberi Anda diskon harga jika Anda memiliki jumlah barang tertentu dari dua kategori yang berbeda? Apakah itu masuk
Product
kelas,Category
kelas,DiscountRule
kelas,CategoryManager
kelas, atau apakah kita memerlukan sesuatu yang baru? Ini adalah bagaimana kita berakhir dengan hal-hal sepertiDiscountRuleProductCategoryFactoryBuilder
.Dalam kode fungsional, hierarki data Anda sepenuhnya ortogonal untuk fungsi Anda. Anda dapat mengurutkan fungsi Anda dengan cara apa pun yang masuk akal semantik. Misalnya, Anda dapat mengelompokkan semua fungsi yang mengubah harga produk secara bersamaan, dalam hal ini masuk akal untuk memfaktorkan fungsionalitas umum seperti
mapPrices
pada contoh Scala berikut:Aku mungkin bisa menambahkan fungsi-fungsi yang berhubungan dengan harga lain di sini seperti
decreasePrice
,applyBulkDiscount
, dllKarena kami juga menggunakan koleksi
Products
, versi OOP perlu menyertakan metode untuk mengelola koleksi itu, tetapi Anda tidak ingin modul ini membahas pemilihan produk, Anda menginginkannya mengenai harga. Coupling fungsi-data memaksa Anda untuk melemparkan boilerplate manajemen pengumpulan di sana juga.Anda dapat mencoba menyelesaikan ini dengan menempatkan
products
anggota dalam kelas yang terpisah, tetapi kemudian Anda berakhir dengan kelas yang sangat erat. Pemrogram OO menganggap kopling fungsi-data sebagai sangat alami dan bahkan bermanfaat, tetapi ada biaya tinggi yang terkait dengannya dalam kehilangan fleksibilitas. Setiap kali Anda membuat fungsi, Anda harus menetapkannya ke satu dan hanya satu kelas. Kapan pun Anda ingin menggunakan suatu fungsi, Anda harus menemukan cara untuk mendapatkan data yang digabungkan ke titik penggunaan. Batasan itu sangat besar.sumber
Cukup memisahkan data dan fungsi seperti yang penulis maksudkan bisa terlihat seperti ini di F # ("bahasa FP").
Anda dapat melakukan kenaikan harga pada daftar produk dengan cara ini.
Catatan: Jika Anda tidak terbiasa dengan FP, setiap fungsi mengembalikan nilai. Berasal dari bahasa mirip-C, Anda dapat memperlakukan pernyataan terakhir dalam suatu fungsi seolah-olah ada
return
di depannya.Saya menyertakan beberapa anotasi jenis, tetapi seharusnya tidak perlu. pengambil / penyetel tidak diperlukan di sini karena modul tidak memiliki data. Itu memiliki struktur data dan operasi yang tersedia. Ini dapat dilihat dengan
List
baik, yang mengeksposmap
menjalankan fungsi pada setiap elemen dalam daftar, dan mengembalikan hasilnya dalam daftar baru.Perhatikan bahwa modul Produk tidak perlu tahu apa pun tentang perulangan, karena tanggung jawab itu tetap dengan modul Daftar (yang menciptakan kebutuhan untuk perulangan).
sumber
Saya perkenalkan ini dengan fakta bahwa saya bukan ahli pemrograman fungsional. Saya lebih dari orang OOP. Jadi sementara saya cukup yakin di bawah ini adalah bagaimana Anda akan mencapai fungsionalitas yang sama dengan FP, saya bisa saja salah.
Ini adalah Dalam Naskah (maka semua jenis penjelasan). Naskah (seperti javascript) adalah bahasa multi-domain.
Secara rinci (dan sekali lagi, bukan pakar FP), hal yang perlu dipahami adalah bahwa tidak ada banyak perilaku yang ditentukan sebelumnya. Tidak ada metode "kenaikan harga" yang menerapkan kenaikan harga di seluruh daftar, karena tentu saja ini bukan OOP: tidak ada kelas untuk mendefinisikan perilaku tersebut. Alih-alih membuat objek yang menyimpan daftar produk, Anda hanya membuat array produk. Anda kemudian dapat menggunakan prosedur FP standar untuk memanipulasi array ini dengan cara apa pun yang Anda inginkan: filter untuk memilih item tertentu, peta untuk menyesuaikan internal, dll ... Anda berakhir dengan kontrol lebih rinci atas daftar produk Anda tanpa harus dibatasi oleh API yang diberikan oleh SimpleProductManager. Ini mungkin dianggap keuntungan oleh sebagian orang. Juga benar bahwa Anda tidak Anda tidak perlu khawatir tentang bagasi apa pun yang terkait dengan kelas ProductManager. Akhirnya, tidak ada kekhawatiran tentang "SetProducts" atau "GetProducts", karena tidak ada objek yang menyembunyikan produk Anda: sebagai gantinya, Anda hanya memiliki daftar produk yang Anda kerjakan. Sekali lagi, ini mungkin keuntungan atau kerugian tergantung pada keadaan / orang yang Anda ajak bicara. Juga, jelas tidak ada hierarki kelas (yang dia keluhkan) karena tidak ada kelas di tempat pertama. ini mungkin keuntungan atau kerugian tergantung pada keadaan / orang yang Anda ajak bicara. Juga, jelas tidak ada hierarki kelas (yang dia keluhkan) karena tidak ada kelas di tempat pertama. ini mungkin keuntungan atau kerugian tergantung pada keadaan / orang yang Anda ajak bicara. Juga, jelas tidak ada hierarki kelas (yang dia keluhkan) karena tidak ada kelas di tempat pertama.
Saya tidak meluangkan waktu untuk membaca seluruh kata-katanya. Saya menggunakan praktik FP ketika itu nyaman, tapi saya jelas lebih tipe pria OOP. Jadi saya pikir sejak saya menjawab pertanyaan Anda, saya juga akan membuat beberapa komentar singkat tentang pendapatnya. Saya pikir ini adalah contoh yang sangat dibuat-buat yang menyoroti "kelemahan" dari OOP. Dalam kasus khusus ini, untuk fungsionalitas yang ditunjukkan, OOP mungkin over-kill, dan FP mungkin lebih cocok. Kemudian lagi, jika ini untuk sesuatu seperti keranjang belanja, melindungi daftar produk Anda dan membatasi aksesnya adalah (saya pikir) tujuan yang sangat penting dari program ini, dan FP tidak memiliki cara untuk menegakkan hal-hal seperti itu. Sekali lagi, mungkin saja saya bukan ahli FP, tetapi setelah menerapkan keranjang belanja untuk sistem e-commerce, saya lebih suka menggunakan OOP daripada FP.
Secara pribadi saya kesulitan mengambil siapa pun dengan serius yang membuat argumen yang kuat untuk "X hanya mengerikan. Selalu gunakan Y". Pemrograman memiliki berbagai alat dan paradigma karena ada berbagai masalah untuk dipecahkan. FP memiliki tempatnya, OOP memiliki tempatnya, dan tidak ada yang akan menjadi programmer yang hebat jika mereka tidak dapat memahami kekurangan dan kelebihan semua alat kami dan kapan menggunakannya.
** catatan: Jelas ada satu kelas dalam contoh saya: kelas Produk. Dalam hal ini meskipun itu hanya sebuah wadah data bodoh: Saya tidak berpikir saya menggunakannya melanggar prinsip-prinsip FP. Ini lebih dari pembantu untuk memeriksa jenis.
** catatan: Saya tidak ingat dari atas kepala saya dan tidak memeriksa apakah cara saya menggunakan fungsi peta akan memodifikasi produk di tempat, yaitu apakah saya secara tidak sengaja menggandakan harga produk dalam produk asli Himpunan. Itu jelas adalah semacam efek samping yang coba dihindari oleh FP, dan dengan sedikit kode saya pasti bisa memastikan itu tidak terjadi.
sumber
Tampaknya bagi saya SimpleProductManager adalah anak (meluas atau mewarisi) dari sesuatu.
Ini hanya implementasi antarmuka ProductManager yang pada dasarnya adalah kontrak yang mendefinisikan tindakan (perilaku) apa yang harus dilakukan objek.
Jika itu adalah anak-anak (atau lebih baik dikatakan, kelas iherited atau kelas memperluas fungsi kelas lain) itu akan ditulis sebagai:
Jadi pada dasarnya, penulis mengatakan:
Whe memiliki beberapa objek perilaku yaitu: setProducts, peningkatanPrice, getProducts. Dan kami tidak peduli jika objek tersebut memiliki perilaku lain juga atau bagaimana perilaku tersebut diimplementasikan.
Kelas SimpleProductManager mengimplementasikannya. Pada dasarnya, ini mengeksekusi tindakan.
Itu juga bisa disebut PercentagePriceIncreaser karena perilaku utamanya adalah menaikkan harga dengan beberapa nilai persentase.
Tetapi kita juga bisa mengimplementasikan kelas lain: ValuePriceIncreaser yang mana akan:
Dari sudut pandang eksternal, tidak ada yang berubah, antarmuka adalah sama, masih memiliki tiga metode yang sama tetapi perilaku berbeda.
Karena tidak ada yang namanya antarmuka dalam FP maka akan sulit untuk diimplementasikan. Di C, misalnya, kita bisa memegang pointer ke fungsi dan memanggil yang sesuai berdasarkan kebutuhan kita. Pada akhirnya, dalam OOP ia bekerja dengan cara yang sangat mirip, tetapi "otomatis" oleh kompiler.
sumber