Tüm önemli OOP özelliklerini kaybetmeden OOP'deki değişmezliği gerçekten kullanabilir miyiz?


11

Programımdaki nesneleri değişmez hale getirmenin yararlarını görüyorum. Uygulamam için iyi bir tasarım gerçekten derinden düşündüğümde, çoğu zaman doğal olarak nesnelerimin çoğunun değişmez olduğuna ulaşıyorum. Genellikle tüm nesnelerimin değişmez olmasını istediğim noktaya gelir .

Bu soru aynı fikirle ilgilidir, ancak hiçbir cevap değişmezliğe iyi bir yaklaşımın ne olduğunu ve ne zaman kullanılacağını göstermez. Bazı değişmez tasarım kalıpları var mı? Genel fikir, pratikte işe yaramaz olan “değişmeleri için kesinlikle onlara ihtiyaç duymadıkça nesneleri değişmez kılmak” gibi görünmektedir.

Benim tecrübem, değişmezliğin kodumu fonksiyonel paradigmaya gittikçe daha fazla yönlendirmesi ve bu ilerlemenin her zaman gerçekleşmesidir:

  1. Listeler, haritalar vb. Gibi kalıcı (işlevsel anlamda) veri yapılarına ihtiyaç duymaya başladım.
  2. Çapraz referanslar ile çalışmak son derece sakıncalıdır (örneğin, çocuklarını ebeveynlerine gönderme yaparken çocuklarına atıfta bulunan ağaç düğümü), çapraz referansları kullanmama neden olur, bu da veri yapılarımı ve kodumu daha işlevsel hale getirir.
  3. Kalıtım herhangi bir anlam ifade etmeyi bırakır ve onun yerine kompozisyon kullanmaya başlarım.
  4. OOP gibi kapsüllemenin tüm temel fikirleri parçalanmaya başlar ve nesnelerim işlevler gibi görünmeye başlar.

Bu noktada pratik olarak artık OOP paradigmasından hiçbir şey kullanmıyorum ve tamamen işlevsel bir dile geçebiliyorum. Bu yüzden sorum: değişmez OOP tasarımına tutarlı bir yaklaşım var mı ya da değişmez fikri tam potansiyeline götürdüğünüzde, artık her zaman OOP dünyasından hiçbir şeye ihtiyaç duymayan fonksiyonel bir dilde programlama yapmanız gerekiyor mu? Hangi sınıfların değiştirilemez olması ve hangilerinin OOP'un parçalanmamasını sağlamak için değişebilir kalması gerektiğine karar vermek için iyi yönergeler var mı?

Sadece kolaylık olması için bir örnek vereceğim. ChessBoardDeğişmez satranç taşları koleksiyonuna sahip olalım (soyut sınıfı genişletelim)Piece). OOP bakış açısından, bir parça tahtadaki konumundan geçerli hamleler üretmekten sorumludur. Ancak hamleleri oluşturmak için parçanın tahtasına bir referans alması gerekirken, tahtanın parçalarına referans alması gerekir. OOP dilinize bağlı olarak bu değişmez çapraz referansları oluşturmak için bazı hileler var, ancak yönetmek için acı veriyorlar, tahtaya referans verecek bir parçaya sahip değiller. Ama sonra tahta hamlelerini üretemez çünkü tahtanın durumunu bilmiyor. Sonra parça sadece parça tipini ve konumunu tutan bir veri yapısı haline gelir. Daha sonra, her türlü parça için hareket oluşturmak için polimorfik bir fonksiyon kullanabilirsiniz. Bu, işlevsel programlamada mükemmel bir şekilde elde edilebilir, ancak çalışma zamanı kontrolleri ve diğer kötü OOP uygulamaları olmadan OOP'de neredeyse imkansızdır ...


3
Temel soruyu seviyorum, ama detaylarla zorlanıyorum. Örneğin, değişmezliği en üst düzeye çıkardığınızda miras neden anlam ifade etmiyor?
Martin Ba

1
Nesnelerinizin yöntemi yok mu?
Monica


4
"OOP açısından, bir parça tahtadaki konumundan geçerli hamleler üretmekten sorumludur." - Kesinlikle hayır, böyle bir parça tasarlamak muhtemelen SRP'ye zarar verir.
Doc Brown

1
@lishaak "Kalıtım ile ilgili sorunu basit terimlerle açıklamakta zorlanıyorsunuz" çünkü bu bir sorun değil; Kötü tasarım bir sorundur. Kalıtım OOP'un özüdür, eğer olmazsa olmazsa olmazdır . Sözde "kalıtımla ilgili problemler" in çoğu, açık bir geçersiz kılma sözdizimine sahip olmayan dillerle ilgili problemlerdir ve daha iyi tasarlanmış dillerde çok daha az problemlidirler. Sanal yöntemler ve polimorfizm olmadan, OOP'niz yoktur; komik nesne sözdizimi ile yordamsal programlama var. Bundan kaçınıyorsanız OO'nun faydalarını görmemeniz şaşırtıcı değil!
Mason Wheeler

Yanıtlar:


24

Tüm önemli OOP özelliklerini kaybetmeden OOP'deki değişmezliği gerçekten kullanabilir miyiz?

Neden olmasın. Java 8 zaten işlevsel olmadan önce bunu yıllardır yapıyor. Hiç Dizeleri duydun mu? Başından beri güzel ve değişmez.

  1. Listeler, haritalar vb. Gibi kalıcı (işlevsel anlamda) veri yapılarına ihtiyaç duymaya başladım.

Bunlara da ihtiyaç var. Yineleyicilerimi geçersiz kıldım çünkü okurken koleksiyonu mutasyona uğrattınız, sadece kaba.

  1. Çapraz referanslar ile çalışmak son derece sakıncalıdır (örneğin, çocuklarını ebeveynlerine gönderme yaparken çocuklarına atıfta bulunan ağaç düğümü), çapraz referansları kullanmama neden olur, bu da veri yapılarımı ve kodumu daha işlevsel hale getirir.

Dairesel referanslar özel bir cehennem türüdür. Değişmezlik sizi ondan kurtaramaz.

  1. Kalıtım herhangi bir anlam ifade etmeyi bırakır ve onun yerine kompozisyon kullanmaya başlarım.

Burada seninleyim ama değişmezlikle ne ilgisi olduğunu göremiyorum. Kompozisyonu sevmemin nedeni dinamik strateji modelini sevmem değil, soyutlama seviyemi değiştirmeme izin verdiği için.

  1. OOP gibi kapsüllemenin tüm temel fikirleri parçalanmaya başlar ve nesnelerim işlevler gibi görünmeye başlar.

"OOP gibi kapsülleme" fikrinizin ne olduğunu düşünmek için ürperiyorum. Alıcılar ve ayarlayıcılar içeriyorsa, lütfen bu kapsüllemeyi aramayı bırakın, çünkü değil. Asla olmadı. Manuel Aspect Odaklı Programlama. Doğrulama şansı ve bir kırılma noktası koymak için bir yer güzel ama kapsülleme değil. Kapsülleme, içeride neler olup bittiğini bilmeme ya da önemseme hakkımı koruyor.

Nesnelerinizin işlevlere benzemesi gerekiyor. Bunlar çantalar. Birlikte hareket eden ve kendilerini yeniden tanımlayan işlevler.

İşlevsel programlama şu anda çok popüler ve insanlar OOP hakkında bazı yanlış düşüncelere yol açıyor. Bunun OOP'un sonu olduğuna inanmanıza izin vermemenize izin vermeyin. Fonksiyonel ve OOP birlikte güzelce yaşayabilir.

  • İşlevsel programlama ödevler hakkında biçimseldir.

  • OOP fonksiyon göstergeleri hakkında resmi.

Gerçekten. Dykstra bize gotozararlı olduğunu söyledi , bu yüzden resmi bir hal aldık ve yapılandırılmış programlama yarattık. Aynen bu iki paradigma, bu zahmetli şeyleri rahatlıkla yapmaktan kaynaklanan tuzaklardan kaçınırken işleri halletmenin yollarını bulmakla ilgilidir.

Size bir şey göstereyim:

f n (x)

Bu bir işlev. Aslında fonksiyonların bir sürekliliği:

f 1 (x)
f 2 (x)
...
f n (x)

Tahmin et bunu OOP dillerinde nasıl ifade ediyoruz?

n.f(x)

Bu kadar az nşey, hangi uygulamanın uygulanacağını seçer fve bu işlevde kullanılan sabitlerin bazılarının ne olduğuna karar verir (bu açıkçası aynı şey anlamına gelir). Örneğin:

f 1 (x) = x + 1
f 2 (x) = x + 2

Kapakların sağladığı şey de aynı. Kapanışlar çevreleyen kapsamlarına, nesne yöntemleri ise örnek durumlarına başvurur. Nesneler kapanışları daha iyi yapabilir. Kapatma, başka bir işlevden döndürülen tek işlevdir. Bir kurucu, tüm fonksiyonlar için bir referans döndürür:

g 1 (x) = x 2 + 1
g 2 (x) = x 2 + 2

Evet, tahmin ettin:

n.g(x)

f ve g birlikte değişen ve birlikte hareket eden işlevlerdir. Bu yüzden onları aynı çantaya yapıştırıyoruz. Bir cismin gerçekte böyledir. Holding nsabiti (değişmez) sadece bunları çağırdığınızda bu ne yapacağını tahmin etmek kolay olacağı anlamına gelir.

Şimdi sadece yapı bu. OOP hakkında düşünme şeklim, diğer küçük şeylerle konuşan bir grup küçük şey. Umarım küçük bir şey küçük bir grup seçin. Kodladığımda kendimi nesne olarak hayal ediyorum. Olaylara nesnenin bakış açısından bakıyorum. Ve tembel olmaya çalışıyorum, bu yüzden nesneyi fazla çalıştırmıyorum. Basit mesajlar alıyorum, üzerinde biraz çalışıyorum ve sadece en iyi arkadaşlarıma basit mesajlar gönderiyorum. Bu nesneyi bitirdiğimde başka bir nesneye atlayıp olaylara onun bakış açısından bakıyorum.

Sınıf Sorumluluk Kartları bana bu şekilde düşünmeyi öğreten ilk kişilerdi. Adamım o zamanlar onlar hakkında kafam karışmıştı ama bugün hala ilgili değilse lanet olsun.

Değişmez satranç taşlarının değişmez bir koleksiyonu olarak bir ChessBoard'a sahip olalım (soyut sınıf parçasını genişleterek). OOP bakış açısından, bir parça tahtadaki konumundan geçerli hamleler üretmekten sorumludur. Ancak hamleleri oluşturmak için parçanın tahtasına bir referans alması gerekirken, tahtanın parçalarına referans alması gerekir.

Arg! Yine gereksiz dairesel referanslarla.

Nasıl olur: A ChessBoardDataStructure, xy kablolarını parça referanslarına dönüştürür. Bu parçalar, x, y ve belirli bir yöntemi alıp ChessBoardDataStructureyeni bir marka şaplak koleksiyonuna dönüştüren bir yönteme sahiptir ChessBoardDataStructure. Sonra bunu en iyi hamleyi seçebilecek bir şeye iter. Şimdi ChessBoardDataStructuredeğişmez olabilir ve parçalar da olabilir. Bu şekilde bellekte sadece bir beyaz piyonunuz olur. Sadece doğru xy konumlarında sadece birkaç referans var. Nesneye yönelik, işlevsel ve değişmez.

Bekle, daha önce satranç hakkında konuşmadık mı?


6
Herkes bunu okumalı. Ve sonra William R. Cook tarafından tekrar ziyaret edilen Veri Soyutlamasını Anlama konusunu okuyun . Ve sonra tekrar okuyun. Ve sonra Cook'un "Nesne" ve "Nesneye Dayalı" Modern, Modern Tanımları için Önerisini okuyun . Alan Kay'ın " Büyük fikir 'mesajlaşmadır ",
OO'nun


1
@Euphoric: Bence "fonksiyonel programlama", "zorunlu programlama", "mantık programlama", vb. İçin standart tanımlarla eşleşen oldukça standart bir formülasyon. Aksi takdirde, C işlevsel bir dildir, çünkü içinde FP kodlayabilir, bir mantık dili, çünkü içinde mantık programlamasını, dinamik bir dili kodlayabiliyorsunuz, çünkü içinde dinamik yazmayı, bir aktör dilini kodlayabiliyorsunuz, çünkü içinde bir aktör sistemini kodlayabiliyorsunuz vb. Ve Haskell, dünyanın en büyük zorunlu dilidir çünkü yan etkileri isimlendirebilir, değişkenlerde saklayabilir, bunları şu şekilde aktarabilirsiniz…
Jörg W Mittag

1
Sanırım Matthias Felleisen'in dâhili anlatımıyla ilgili dahi kağıdındakilere benzer fikirler kullanabiliriz. Bir OO programını örneğin Java'dan C♯'ye taşımak yalnızca yerel dönüştürmelerle yapılabilir, ancak C'ye taşınması için küresel bir yeniden yapılandırma gerekir (temel olarak, bir mesaj gönderme mekanizması tanıtmanız ve tüm işlev çağrıları üzerinden yönlendirmeniz gerekir), Java ve C♯ OO ifade edebilir ve C "sadece" kodlayabilir.
Jörg W Mittag

1
@lishaak Çünkü farklı şeyleri belirtiyorsunuz. Bu, onu bir soyutlama düzeyinde tutar ve aksi takdirde tutarsız hale gelebilecek konum bilgisi depolamasının kopyalanmasını önler. Ekstra yazmayı yeniden gönderirseniz, yalnızca bir kez yazmanız için bir yönteme yapıştırın ... Bir parça nerede olduğunu söylerseniz nerede olduğunu hatırlamak zorunda değildir. Artık parçalar yalnızca renk, hareket ve görüntü / kısaltma gibi her zaman doğru ve değişmez olan bilgileri depolar.
candied_orange

2

OOP tarafından ana akıma getirilen en yararlı kavramlar, aklımda:

  • Uygun modülerleştirme.
  • Veri kapsülleme.
  • Özel ve genel arayüzler arasında açık bir ayrım.
  • Kod genişletilebilirliği için açık mekanizmalar.

Tüm bu avantajlar, miras ve hatta sınıflar gibi geleneksel uygulama detayları olmadan da gerçekleştirilebilir. Alan Kay'ın orijinal "nesne yönelimli sistem" fikri, "yöntemler" yerine "mesajlar" kullandı ve Erlang'a, örneğin C ++ 'dan daha yakındı. Birçok geleneksel OOP uygulama detayını ortadan kaldıran, ancak yine de makul derecede nesne yönelimli hisseden Go'ya bakın.

Değişmez nesneler kullanıyorsanız, geleneksel OOP öğelerinin çoğunu hala kullanabilirsiniz: arayüzler, dinamik gönderim, kapsülleme. Ayarlayıcılara ve genellikle daha basit nesneler için alıcılara bile ihtiyacınız yoktur. Ayrıca, değişmezliğin avantajlarından da yararlanabilirsiniz: bir nesnenin bu arada değişmediğinden, savunma amaçlı kopyalamanın, veri yarışının olmadığından ve yöntemleri saflaştırmanın her zaman eminiz.

Scala'nın değişmezliği ve FP yaklaşımlarını OOP ile nasıl birleştirmeye çalıştığına bir göz atın. Kuşkusuz bu en basit ve zarif dil değil. Yine de, pratik olarak başarılı. Ayrıca, benzer bir karışım için birçok araç ve yaklaşım sunan Kotlin'e bir göz atın.

N yıl önce, içerik oluşturucuların aklındaki dilden farklı bir yaklaşım denemede karşılaşılan sorun , standart kütüphaneyle 'empedans uyumsuzluğu'dur. OTOH hem Java hem de .NET ekosistemlerinde şu anda değişmez veri yapıları için makul standart kitaplık desteğine sahiptir; Tabii ki üçüncü taraf kütüphaneleri de var.

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.