OO olmayanlarda bu nasıl programlanır? [kapalı]


11

Başka bir paradigma lehine OOP'nin aşağı tarafındaki korkunç bir makaleyi okurken çok fazla hata bulamadığım bir örneğe girdim.

Yazarın argümanlarına açık olmak istiyorum ve teorik olarak puanlarını anlayabilsem de, özellikle bir örnek, bir FP dilinde nasıl daha iyi uygulanacağını hayal etmeye çalışırken zorlanıyorum.

Gönderen: 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.

Yazarın "Bu 3 davranışın veri hiyerarşisine bağlanmasının mantıklı bir nedeni var mı?"

Özellikle sorduğum şey, bu örneğin bir FP dilinde nasıl modelleneceği / programlanacağı (Gerçek kod, teorik olarak değil)?


44
Herhangi bir programlama paradigmasını böyle kısa ve tutarlı örneklerle karşılaştırmayı makul bir şekilde bekleyemezsiniz. Buradaki herkes, özellikle tercih ettikleri paradigmayı dinlenmeden daha iyi gösteren kod gereksinimleri bulabilir, özellikle de başkalarını yanlış uygularlarsa. Yalnızca gerçek, büyük, değişen bir projeniz olduğunda, farklı paradigmaların güçlü ve zayıf yönleri hakkında bilgi edinebilirsiniz.
Euphoric

20
OO programlamasında bu 3 yöntemin aynı sınıfta bir araya gelmesini zorunlu kılan hiçbir şey yoktur; benzer şekilde davranışların verilerle aynı sınıfta varlığını zorunlu kılan OO programlama hakkında hiçbir şey yoktur. Yani OO Programlama ile verileri davranışla aynı sınıfa koyabilir veya ayrı bir varlık / modele bölebilirsiniz. her iki durumda da, OO'nun verilerin bir nesne ile nasıl ilişkilendirilmesi gerektiği hakkında söyleyecek hiçbir şeyi yoktur, çünkü bir nesne kavramı temel olarak mantıksal olarak ilgili yöntemleri bir sınıfa gruplayarak davranış modelleme ile ilgilidir .
Ben Cottrell

20
Bir makale aralığına 10 cümle aldım ve vazgeçtim. Perdenin arkasındaki adama dikkat etmeyin. Diğer yandan True Scotsmen'ların öncelikle OOP programcıları olduğu konusunda hiçbir fikrim yoktu.
Robert Harvey

11
Yine bir diğeri prosedür kodunu OO dilinde yazan birinden geliyor, sonra OO'nun neden çalışmadığını merak ediyor.
TheCatWhisperer

11
Şüphesiz, OOP'un baştan sona tasarım yanlışlarının bir felaketi olduğu doğru olsa da - bunun bir parçası olmaktan gurur duyuyorum! - bu makale okunamıyor ve verdiğiniz örnek temelde kötü tasarlanmış bir sınıf hiyerarşisinin kötü tasarlanmış olduğunu iddia ediyor.
Eric Lippert

Yanıtlar:


42

FP tarzında, Productdeğişmez bir sınıf product.setPriceolurdu , bir Productnesneyi değiştirmez , onun yerine yeni bir nesne döndürür ve increasePriceişlev "bağımsız" bir işlev olur. Sizinki gibi benzer görünümlü bir sözdizimi kullanarak (C # / Java gibi), eşdeğer bir işlev şöyle görünebilir:

 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;
}

Gördüğünüz gibi, burada esas olan OOP örneğindeki "boilerplate" kodu hariç, çekirdek gerçekten farklı değil. Bununla birlikte, bunu OOP'un şişirilmiş koda yol açtığının kanıtı olarak görmüyorum, sadece bir tanesi yeterince yapay bir kod örneği oluşturuyorsa, herhangi bir şeyi kanıtlamak mümkündür.


7
Bu "daha fazla FP" yapmanın yolları: 1) Kısmi işlevler yerine toplam işlevlerin yazılmasını kolaylaştırmak için "/ (x! = Null)" soyutlamak için üst düzey yardımcı işlevler kullanmak için nullability yerine Belki / İsteğe bağlı türleri kullanın. mantık. 2) Ürünün fiyatına lens bağlamında yüzde artışı uygulamak açısından tek bir ürün için artan fiyatı tanımlamak amacıyla lensleri kullanın. 3) Harita / Arama çağrısı için açık bir lambda kullanmaktan kaçınmak için kısmi uygulama / kompozisyon / körelme kullanın.
Jack,

6
Bir koleksiyon fikrinin tasarım gereği boş olmak yerine boş olabileceğinden nefret etmem gerektiğini söylemeliyim. Yerel grup / toplama desteğine sahip işlevsel diller bu şekilde çalışır. OOP'ta bile nullbir koleksiyonun dönüş türü olduğu yerden geri dönmekten nefret ediyorum . / rant over
Berin Loritsch

Ancak bu, Java veya C # gibi OOP dillerindeki bir yardımcı program sınıfındaki gibi statik bir yöntem olabilir. Bu kod kısmen daha kısadır çünkü listeden geçip kendiniz tutmamanızı istersiniz. Orijinal kod ayrıca bir veri yapısına sahiptir ve sadece dışarı taşımak, orijinal kodu kavramlarda bir değişiklik olmadan daha kısa hale getirir.
Mark

@ Mark: tabii, ve bence OP bunu zaten biliyor. Soruyu OOP olmayan bir dilde zorunlu olmayan "bunu işlevsel bir şekilde nasıl ifade edeceğim" olarak anlıyorum.
Doc Brown

@ Mark FP ve OO birbirini dışlamaz.
Pieter B

17

Özellikle sorduğum şey, bu örneğin bir FP dilinde nasıl modelleneceği / programlanacağı (Gerçek kod, teorik olarak değil)?

"A" FP dilinde mi? Herhangi bir yeterlilik varsa, Emacs lisp'i seçerim. Türleri (bir çeşit, bir çeşit) kavramı var, ama sadece yerleşik olanlar. Böylece örneğiniz "bir listedeki her öğeyi bir şeyle nasıl çarpıp yeni bir liste döndürürsünüz" şeklinde azalır.

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

İşte böyle. Diğer diller benzer olacaktır, fark olağan işlevsel "eşleştirme" anlambilimiyle müstehcen türlerden faydalanmanızdır. Haskell'e göz atın:

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

(Ya da böyle bir şey, çağlar oldu ...)

Yazarın argümanlarına açık olmak istiyorum,

Neden? Makaleyi okumaya çalıştım; Bir sayfadan vazgeçmek zorunda kaldım ve gerisini hızlıca taradım.

Makalenin sorunu, OOP'a karşı olması değil. Ben de körü körüne "pro OOP" değilim. Mantıksal, işlevsel ve OOP paradigmalarıyla, çoğu zaman aynı dilde ve sık sık üçünün hiçbiri olmadan, hatta meclis düzeyinde bile olmadan programladım. Ben istiyorum asla bu paradigmaların herhangi ölçüde her açıdan diğer superiour söylüyorlar. X dilini Y'den daha iyi istediğimi iddia edebilir miyim ? Tabi ki isterim! Ama bu makalenin konusu bu değil.

Makalenin sorunu, ilk cümleden son cümleye bol miktarda retorik araç (yanlış) kullanmasıdır. İçerdiği tüm hataları açıklamaya başlamak tamamen boştur. Yazar, tartışmaya sıfır ilgisi olduğunu açıkça gösteriyor, bir haçlı seferi yapıyor. Ne gereği var?

Günün sonunda tüm bunlar sadece bir iş yapmak için araçlar. OOP'nin daha iyi olduğu işler olabilir ve FP'nin daha iyi olduğu ya da her ikisinin de aşırı olduğu diğer işler olabilir. Önemli olan iş için doğru aleti seçmek ve işi halletmektir.


4
"Tartışmaya sıfır ilgisi olduğu açıktır, bir haçlı seferi üzerindedir" Bu mücevher için bir oy verin.
Euphoric

Haskell kodunuzda Num sınırlamasına ihtiyacınız yok mu? aksi halde (*) nasıl arayabilirsiniz?
jk.

@jk., Haskell'i yaptığım çağlar oldu, bu sadece OP'nin aradığı cevap konusundaki kısıtlamasını karşılamaktı. ;) Birisi kodumu düzeltmek istiyorsa, çekinmeyin. Ama eminim, onu Num.
AnoE

7

Yazar çok iyi bir noktaya değindi, sonra onu desteklemeye çalışmak için cansız bir örnek seçti. Şikayet sınıfın uygulanmasıyla ilgili değildir, veri hiyerarşisinin işlev hiyerarşisi ile ayrılmaz bir şekilde eşleştirildiği fikrindedir.

Daha sonra, yazarın bakış açısını anlamak için, bu tek sınıfı işlevsel bir tarzda nasıl uygulayacağını görmeye yardımcı olmayacaktır. Bu sınıfın etrafındaki tüm veri ve fonksiyon bağlamını işlevsel bir tarzda nasıl tasarlayacağını görmeniz gerekir .

Ürünler ve fiyatlandırmayla ilgili potansiyel veri türlerini düşünün. Birkaç beyin fırtınası yapmak için: isim, upc kodu, kategori, nakliye ağırlığı, fiyat, para birimi, indirim kodu, indirim kuralı.

Bu, nesne yönelimli tasarımın kolay kısmıdır. Yukarıdaki tüm nesneler için sadece bir sınıf oluşturuyoruz ve iyiyiz, değil mi? ProductBunlardan birkaçını birleştirmek için bir sınıf mı yapıyorsunuz?

Ancak bekleyin, şu türlerden bazılarının koleksiyonlarına ve toplamalarına sahip olabilirsiniz: [kategori], (indirim kodu -> fiyat), (miktar -> indirim tutarı) vb. Bunlar nereye uyuyor? CategoryManagerTüm farklı kategorileri takip etmek için ayrı mı oluşturuyoruz , yoksa bu sorumluluk Categorydaha önce oluşturduğumuz sınıfa mı ait?

Şimdi, iki farklı kategoriden belirli bir miktar ürününüz varsa size bir fiyat indirimi veren işlevler ne olacak? O gitmek mu Productsınıf, Categorysınıf, DiscountRulesınıf, CategoryManagersınıf, ya da biz yeni bir şey gerekiyor? Böyle şeylerle sonuçlanırız DiscountRuleProductCategoryFactoryBuilder.

İşlevsel kodda, veri hiyerarşiniz işlevlerinize tamamen diktir. Anlamsal anlamı ne olursa olsun işlevlerinizi sıralayabilirsiniz. Örneğin, ürünlerin fiyatlarını değiştiren tüm işlevleri birlikte gruplayabilirsiniz, bu durumda mapPricesaşağıdaki Scala örneğindeki gibi ortak işlevselliği hesaba katmak mantıklı olacaktır :

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)

Muhtemelen gibi burada diğer fiyatla ilgili işlevler ekleyebilirsiniz decreasePrice, applyBulkDiscountvb

Ayrıca bir koleksiyon kullandığımız için Products, OOP sürümünün bu koleksiyonu yönetmek için yöntemler içermesi gerekir, ancak bu modülün ürün seçimi ile ilgili olmasını istemediniz, fiyatlar hakkında olmasını istediniz. Fonksiyon-veri bağlantısı sizi toplama yönetim kazanını da atmaya zorladı.

Bunu, productsüyeyi ayrı bir sınıfa koyarak çözmeyi deneyebilirsiniz , ancak daha sonra sıkı sıkıya bağlı sınıflarla sonuçlanırsınız. OO programcıları, işlev-veri bağlantısının çok doğal ve hatta faydalı olduğunu düşünür, ancak esneklik kaybında bununla ilişkili yüksek bir maliyet vardır. Bir işlev oluşturduğunuzda, onu yalnızca bir sınıfa atamanız gerekir. Bir işlevi kullanmak istediğinizde, birleştirilmiş verilerini kullanım noktasına getirmenin bir yolunu bulmalısınız. Bu kısıtlamalar çok büyük.


2

Verileri ve işlevi yazarın bahsettiği gibi ayırmak, F # ("bir FP dili") içinde böyle görünebilir.

module Product =

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

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

        { product with Price = newPrice }

Bu şekilde bir ürün listesinde fiyat artışı yapabilirsiniz.

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

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

Not: FP'ye aşina değilseniz, her işlev bir değer döndürür. C benzeri bir dilden gelirseniz, son ifadeyi sanki returnönünde bir işlevi varmış gibi görürsünüz .

Bazı ek açıklamalar ekledim, ancak gereksiz olmalılar. modülün veri sahibi olmadığından getter / setter burada gereksizdir. Verilerin yapısına ve mevcut işlemlere sahiptir. Bu görülebilir Listortaya hangi sıra mapyeni listede sonucu listesindeki her eleman üzerinde bir işlevi çalıştırmak için, ve döner.

Ürün modülünün döngü hakkında hiçbir şey bilmek zorunda olmadığına dikkat edin, çünkü sorumluluk Liste modülüne aittir (döngü gereksinimini yaratan).


1

İşlevsel programlama konusunda uzman olmadığımla bunu önceden yazayım. Ben daha çok OOP'lu biriyim. Bu yüzden, FP ile aynı tür işlevleri nasıl yapacağınız konusunda emin olduğum halde, yanlış olabilirim.

Bu Daktilo Yazısı'dır (dolayısıyla tüm tür ek açıklamaları). Daktilo metni (javascript gibi) çok alanlı bir dildir.

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 );

Ayrıntılı olarak (ve bir FP uzmanı değil), anlaşılması gereken şey önceden tanımlanmış çok fazla davranış olmamasıdır. Tüm liste boyunca bir fiyat artışı uygulayan bir "fiyatı artır" yöntemi yoktur, çünkü elbette bu OOP değildir: bu tür bir davranışın tanımlanacağı sınıf yoktur. Bir ürün listesini saklayan bir nesne oluşturmak yerine, sadece bir ürün dizisi yaratırsınız. Daha sonra bu diziyi istediğiniz şekilde manipüle etmek için standart FP prosedürlerini kullanabilirsiniz: belirli öğeleri seçmek için filtre, iç kısımları ayarlamak için harita, vb. ... SimpleProductManager'ın size verdiği API. Bu bazıları tarafından bir avantaj olarak düşünülebilir. Ayrıca, t ProductManager sınıfıyla ilişkili herhangi bir bagaj için endişelenmeniz gerekmez. Son olarak, ürünlerinizi saklayan bir nesne olmadığı için "SetProducts" veya "GetProducts" hakkında endişe yoktur: bunun yerine yalnızca üzerinde çalıştığınız ürünlerin listesine sahipsiniz. Yine, konuştuğunuz koşullara / kişiye bağlı olarak bu bir avantaj veya dezavantaj olabilir. Ayrıca, açıkçası hiçbir sınıf hiyerarşisi yoktur (bu şikayet ettiği şeydir) çünkü ilk etapta hiçbir sınıf yoktur. bu, konuştuğunuz koşullara / kişiye bağlı olarak bir avantaj veya dezavantaj olabilir. Ayrıca, açıkçası hiçbir sınıf hiyerarşisi yoktur (bu şikayet ettiği şeydir) çünkü ilk etapta hiçbir sınıf yoktur. bu, konuştuğunuz koşullara / kişiye bağlı olarak bir avantaj veya dezavantaj olabilir. Ayrıca, açıkçası hiçbir sınıf hiyerarşisi yoktur (bu şikayet ettiği şeydir) çünkü ilk etapta hiçbir sınıf yoktur.

Onun bütün rantını okumak için zaman ayırmadım. Uygun olduğunda FP uygulamalarını kullanıyorum, ama kesinlikle daha çok OOP tipi bir adamım. Bu yüzden, sorunuzu cevapladığımdan beri, fikirleri hakkında da kısa yorumlar yapacağımı düşündüm. Bu OOP "dezavantajları" vurgulayan çok entrived bir örnek olduğunu düşünüyorum. Bu özel durumda, gösterilen işlevsellik için, OOP muhtemelen aşırı öldürücüdür ve FP muhtemelen daha iyi bir seçim olacaktır. Daha sonra, bu bir alışveriş sepeti gibi bir şey için olsaydı, ürün listenizi korumak ve erişimi sınırlamak (bence) programın çok önemli bir hedefidir ve FP'nin bu tür şeyleri zorlamanın bir yolu yoktur. Yine, sadece bir FP uzmanı değilim, ancak e-ticaret sistemleri için alışveriş sepetleri uyguladıktan sonra, OOP'yi FP'den çok kullanmayı tercih ederim.

Şahsen ben "X sadece korkunç. Her zaman Y kullanın" için bu kadar güçlü bir argüman yapar kimseyi ciddiye almakta zorlanıyorum. Programlamanın çeşitli araçları ve paradigmaları vardır, çünkü çözülmesi gereken çok çeşitli problemler vardır. FP'nin yeri var, OOP'un yeri var ve tüm araçlarımızın dezavantajlarını ve avantajlarını ve ne zaman kullanacaklarını anlayamazlarsa kimse harika bir programcı olmayacak.

** not: Açıkçası benim örneğimde bir sınıf var: Ürün sınıfı. Bu durumda, sadece aptal bir veri kabı olsa da: Onu kullanmamın FP ilkelerini ihlal ettiğini düşünmüyorum. Daha çok tip kontrolü için bir yardımcıdır.

** not: Başımın üstünü hatırlamıyorum ve harita işlevini kullanma şeklimin ürünleri yerinde değiştirip değiştirmeyeceğini kontrol etmedim, yani orijinal ürünlerdeki ürünlerin fiyatını yanlışlıkla iki katına çıkardım dizi. Açıkçası FP'nin kaçınmaya çalıştığı bir yan etki türüdür ve biraz daha fazla kodla kesinlikle olmadığından emin olabilirim.


2
Bu klasik anlamda gerçekten bir OOP örneği değil. Gerçek OOP'de veriler davranışla birleştirilir; burada ikisini ayırdınız. Mutlaka kötü bir şey değildir (aslında daha temiz buluyorum), ancak klasik OOP dediğim şey bu değil.
Robert Harvey

0

Bana öyle geliyor ki SimpleProductManager bir şeyin çocuğu (genişletiyor veya miras).

Temel olarak nesnenin hangi eylemleri (davranışları) yapması gerektiğini tanımlayan bir sözleşme olan ProductManager arabiriminin uygulanması.

Bir çocuk (ya da daha iyisi söylenmiş, başka bir sınıf işlevselliğini genişleten miras sınıf veya sınıf) olurdu:

class SimpleProductManager extends ProductManager {
    ...
}

Yani temelde yazar diyor ki:

Hangi davranış bazı nesneler var: setProducts, artırmakFiyat, getProducts. Ve nesnenin başka bir davranışı olup olmadığını ya da davranışın nasıl uygulandığını umursamıyoruz.

SimpleProductManager sınıfı bunu uygular. Temel olarak, eylemleri yürütür.

Ana davranışı fiyatı bir miktar yüzde arttırmak olduğu için PerprintPriceIncreaser olarak da adlandırılabilir.

Ancak başka bir sınıf da uygulayabiliriz: ValuePriceIncreaser hangi davranışın olacağı:

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

Dış bakış açısından, hiçbir şey değişmedi, arayüz aynı, hala aynı üç yönteme sahip, ancak davranış farklı.

FP'de arayüzler diye bir şey olmadığından uygulanması zor olacaktır. Örneğin, C olarak, fonksiyonlara işaret edebilir ve ihtiyaçlarımıza göre uygun olanı çağırabiliriz. Sonunda, OOP çok benzer bir şekilde çalışır, ancak derleyici tarafından "otomatik".

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.