Bagaimana ini diprogram dalam non-OO? [Tutup]

11

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.

Dari: http://www.smashcompany.com/technology/object-oriented-programming-is-an-expensive-disaster-which-must-end

// 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)?

Danny Yaroslavski
sumber
44
Anda tidak dapat secara wajar berharap untuk membandingkan paradigma pemrograman apa pun pada contoh-contoh singkat dan dibuat-buat. Siapa pun di sini dapat membuat persyaratan kode yang membuat paradigma pilihan mereka terlihat lebih baik daripada yang lain, terutama jika mereka menerapkan yang lain dengan tidak tepat. Hanya ketika Anda memiliki proyek yang nyata, besar, dan berubah, Anda dapat memperoleh wawasan tentang kekuatan dan kelemahan berbagai paradigma.
Euforia
20
Tidak ada apa-apa tentang pemrograman OO yang mengamanatkan bahwa 3 metode harus berjalan bersama di kelas yang sama; sama halnya tidak ada tentang pemrograman OO yang mengamanatkan bahwa perilaku harus ada di kelas yang sama dengan data. Dengan kata lain, dengan Pemrograman OO Anda dapat menempatkan data di kelas yang sama dengan perilaku, atau Anda dapat membaginya menjadi entitas / model yang terpisah. Bagaimanapun juga, OO tidak benar-benar mengatakan tentang bagaimana data harus berhubungan dengan suatu objek, karena konsep suatu objek pada dasarnya berkaitan dengan perilaku pemodelan dengan mengelompokkan metode yang berhubungan secara logis ke dalam kelas.
Ben Cottrell
20
Saya mendapat 10 kalimat dalam kata-kata kasar dari sebuah artikel dan menyerah. Tidak memperhatikan pria di balik tirai itu. Dalam berita lain, saya tidak tahu bahwa True Scotsmen terutama adalah programmer OOP.
Robert Harvey
11
Namun kata-kata kasar lain dari seseorang yang menulis kode prosedural dalam bahasa OO, kemudian bertanya-tanya mengapa OO tidak bekerja untuknya.
TheCatWhisperer
11
Meskipun tidak diragukan lagi benar bahwa OOP adalah bencana kesalahan langkah desain dari awal hingga akhir - dan saya bangga menjadi bagian darinya! - artikel ini tidak dapat dibaca, dan contoh yang Anda berikan pada dasarnya membuat argumen bahwa hierarki kelas yang dirancang dengan buruk dirancang dengan buruk.
Eric Lippert

Jawaban:

42

Dalam gaya FP, Productakan menjadi kelas yang tidak dapat diubah, product.setPricetidak akan bermutasi Productobjek tetapi mengembalikan objek baru, dan increasePricefungsi akan menjadi fungsi "mandiri". Menggunakan sintaks yang mirip dengan milik Anda (mirip C # / Java), fungsi yang setara bisa terlihat seperti ini:

 public List increasePrice(List products, int percentage) {
    if (products != null) {
        return products.Select(product => {
                double newPrice = product.getPrice().doubleValue() *
                    (100 + percentage)/100;
                return product.setPrice(newPrice);     
               });
    }
    else return null;
}

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.

Doc Brown
sumber
7
Cara untuk membuat ini "lebih banyak FP": 1) Gunakan Mungkin / Jenis opsional bukan nullability untuk membuatnya lebih mudah untuk menulis fungsi total daripada fungsi parsial dan menggunakan fungsi pembantu tingkat tinggi untuk abstrak "jika (x! = Null)" logika. 2) Gunakan lensa untuk menentukan kenaikan harga untuk satu produk dalam hal menerapkan persentase kenaikan dalam konteks lensa pada harga produk. 3) Gunakan sebagian aplikasi / komposisi / kari untuk menghindari lambda eksplisit untuk peta / Pilih panggilan.
Jack
6
Harus bilang aku benci ide koleksi bisa nol, bukan hanya kosong oleh desain. Bahasa fungsional dengan dukungan tuple / koleksi asli beroperasi dengan cara itu. Bahkan di OOP saya benci kembali di nullmana koleksi adalah tipe pengembalian. / kata-kata kasar
Berin Loritsch
Tapi ini bisa menjadi metode statis seperti di kelas utilitas dalam bahasa OOP seperti Java atau C #. Kode ini lebih pendek sebagian karena Anda meminta untuk memasukkan dalam daftar dan tidak menahannya sendiri. Kode asli juga memegang struktur data dan hanya memindahkannya akan membuat kode asli lebih pendek tanpa perubahan konsep.
Tandai
@ Mark: yakin, dan saya pikir OP sudah tahu ini. Saya memahami pertanyaan sebagai "bagaimana cara mengekspresikannya dengan cara yang fungsional", tidak wajib dalam bahasa non-OOP.
Doc Brown
@Mark FP dan OO tidak mengecualikan satu sama lain.
Pieter B
17

Apa yang secara khusus saya tanyakan adalah bagaimana contoh ini akan dimodelkan / diprogram dalam bahasa FP (Kode aktual, bukan secara teoritis)?

Dalam 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".

(mapcar (lambda (x) (* x 2)) '(1 2 3))

Ini dia. Bahasa lain akan serupa, dengan perbedaan bahwa Anda mendapatkan manfaat dari jenis eksplisit dengan semantik "pencocokan" fungsional yang biasa. Lihat Haskell:

incPrice :: (Num) -> [Num] -> [Num]  
incPrice _ [] = []  
incPrice percentage (x:xs) = x*percentage : incPrice percentage xs  

(Atau sesuatu seperti itu, sudah lama ...)

Saya ingin terbuka pada argumen penulis,

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.

AnoE
sumber
4
"Sangat jelas bahwa dia tidak tertarik dalam diskusi, dia sedang dalam perang salib" Dapatkan dukungan untuk permata ini.
Euforia
Apakah Anda tidak memerlukan batasan Num pada kode Haskell Anda? bagaimana Anda bisa menelepon (*) sebaliknya?
jk.
@jk., sudah lama saya lakukan Haskell, itu hanya untuk memenuhi kendala OP untuk jawaban yang dia cari. ;) Jika seseorang ingin memperbaiki kode saya, jangan ragu. Tapi tentu saja, saya akan beralih ke Bil.
AnoE
7

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 Productkelas 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 CategoryManageruntuk melacak semua jenis kategori yang berbeda, atau apakah tanggung jawab itu milik Categorykelas 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 Productkelas, Categorykelas, DiscountRulekelas, CategoryManagerkelas, atau apakah kita memerlukan sesuatu yang baru? Ini adalah bagaimana kita berakhir dengan hal-hal seperti DiscountRuleProductCategoryFactoryBuilder.

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 mapPricespada contoh Scala berikut:

def mapPrices(f: Int => Int)(products: Traversable[Product]): Traversable[Product] =
  products map {x => x.copy(price = f(x.price))}

def increasePrice(percentage: Int)(price: Int): Int =
  price * (percentage + 100) / 100

mapPrices(increasePrice(25))(products)

Aku mungkin bisa menambahkan fungsi-fungsi yang berhubungan dengan harga lain di sini seperti decreasePrice, applyBulkDiscount, dll

Karena 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 productsanggota 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.

Karl Bielefeldt
sumber
2

Cukup memisahkan data dan fungsi seperti yang penulis maksudkan bisa terlihat seperti ini di F # ("bahasa FP").

module Product =

    type Product = {
        Price : decimal
        ... // other properties not mentioned
    }

    let increasePrice ( percentage : int ) ( product : Product ) : Product =
        let newPrice = ... // calculate

        { product with Price = newPrice }

Anda dapat melakukan kenaikan harga pada daftar produk dengan cara ini.

let percentage = 10
let products : Product list = ...  // load?

products
|> List.map (Product.increasePrice percentage)

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 returndi 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 Listbaik, yang mengekspos mapmenjalankan 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).

Kasey Speakman
sumber
1

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.

export class Product extends Object {
    name: string;
    price: number;
    category: string;
}

products: Product[] = [
    new Product( { name: "Tablet", "price": 20.99, category: 'Electronics' } ),
    new Product( { name: "Phone", "price": 500.00, category: 'Electronics' } ),
    new Product( { name: "Car", "price": 13500.00, category: 'Auto' } )
];

// find all electronics and double their price
let newProducts = products
    .filter( ( product: Product ) => product.category === 'Electronics' )
    .map( ( product: Product ) => {
        product.price *= 2;
        return product;
    } );

console.log( newProducts );

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.

Conor Mancone
sumber
2
Ini sebenarnya bukan contoh OOP, dalam pengertian klasik. Dalam OOP sejati, data akan digabungkan dengan perilaku; di sini, Anda sudah memisahkan keduanya. Ini tidak selalu merupakan hal yang buruk (saya benar-benar merasa lebih bersih), tetapi bukan itu yang saya sebut sebagai OOP klasik.
Robert Harvey
0

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:

class SimpleProductManager extends ProductManager {
    ...
}

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:

public void increasePrice(int number) {
    if (products != null) {
        for (Product product : products) {
            double newPrice = product.getPrice() + number;
            product.setPrice(newPrice);
        }
    }
}

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.

Fis
sumber