Tam değişkenlik ve Nesneye Dayalı Programlama


43

Çoğu OOP dilinde, nesneler genellikle sınırlı bir istisnalar kümesiyle değiştirilebilir (örneğin python'daki tuples ve stringler gibi). Çoğu işlevsel dilde, veriler değişmez.

Değişebilir ve değişmez nesnelerin her ikisi de kendi avantajlarının ve dezavantajlarının bir listesini verir.

Her iki kavramla da evlenmeye çalışan diller var; örneğin (açıkça ilan ettiğiniz) scala ve değişken veri (örneğin, hatalıysam, lütfen benim ölçeklendirme bilgim daha sınırlı).

Benim soru: Does komple bir cepten bağlamında bir anlam created- olmuştur kez hiçbir nesne -yani (sic!) Değişmezlik mutasyona olabilir?

Böyle bir modelin tasarımları veya uygulamaları var mı?

Temel olarak, (tam) değişmezlik ve OOP karşıtları mı yoksa dikey mi?

Motivasyon: OOP'de normalde veriler üzerinde çalışır , temel bilgileri değiştirir (mutasyona uğrar), bu nesneler arasında referans tutar. Ör . Başka bir nesneyi referans Personalan üyeli bir sınıf nesnesi. Babanın adını değiştirirseniz, bu durum güncellemeye gerek kalmadan hemen alt nesne tarafından görülebilir. Değişmez olmak, hem baba hem de çocuk için yeni nesneler inşa etmeniz gerekir. Ancak, paylaşılan nesneler, çoklu iş parçacığı, GIL, vb. İle daha az uğraşmanız olur.fatherPerson


2
Taklit edilebilirlik, bir OOP dilinde, yalnızca nesne erişim noktalarını verileri mutasyona uğramayan yöntemler veya salt okunur özellikler olarak göstererek simüle edilebilir. Taklit edilebilirlik, bazı işlevsel dil özelliklerini kaçırmış olmanız dışında, herhangi bir işlevsel dilde olduğu gibi OOP dillerinde de aynı şekilde çalışır.
Robert Harvey

4
Değişebilirlik, C # ve Java gibi OOP dillerinin bir özelliği değildir ve değişmezlik de değildir. Sınıfı yazma biçiminizle değişkenlik veya değişkenlik belirlersiniz.
Robert Harvey,

9
Sizin varsayımınız, değişkenliğin nesne yöneliminin temel bir özelliği olduğu görünüyor. Değil. Değişebilirlik sadece nesnelerin veya değerlerin bir özelliğidir. Nesne yönelimi, mutasyonla ilgisi çok az olan veya hiç olmayan bazı içsel kavramları (kapsülleme, polimorfizm, kalıtım vb.) Kapsar ve yine de bu özelliklerin faydalarını elde edersiniz. her şeyi değiştirilemez hale getirmiş olsan bile.
Robert Harvey

2
@MichaelT Soru, belirli şeyleri değiştirilemez kılmakla ilgili değil, her şeyi değiştirilemez kılmakla ilgili.

Yanıtlar:


43

OOP ve değişmezlik birbirine neredeyse tamamen diktir. Ancak, zorunlu programlama ve değişmezlik değildir.

OOP iki temel özellik ile özetlenebilir:

  • Kapsülleme : Nesnelerin içeriğine doğrudan erişmeyeceğim, bunun yerine bu nesneyle belirli bir arayüz (“yöntemler”) ile iletişim kurmaya çalışacağım. Bu arayüz dahili verileri benden gizleyebilir. Teknik olarak, bu, OOP yerine modüler programlamaya özgüdür. Tanımlanmış bir arayüz üzerinden verilere erişmek kabaca soyut bir veri tipine eşdeğerdir.

  • Dinamik Sevk : Bir nesne üzerinde bir yöntem çağırdığımda, yürütülen yöntem çalışma zamanında çözülecek. (Örneğin, sınıf tabanlı OOP'de, sizebir IListörnek için bir yöntem çağırabilirim , ancak çağrı bir LinkedListsınıftaki bir uygulamaya çözülebilir ). Dinamik gönderme, polimorfik davranışa izin vermenin bir yoludur.

Kapsülleme, değişkenlik olmadan daha az anlam ifade eder ( dış karışarak bozulabilecek iç durum yoktur ), ancak yine de her şey değişmez olsa bile soyutlamaları daha kolay hale getirme eğilimindedir.

Bir zorunlu program, sırayla yürütülen ifadelerden oluşur. Bir ifadenin programın durumunu değiştirmek gibi yan etkileri var. Değişmezlik durumunda, devlet değiştirilemez (elbette yeni bir devlet oluşturulabilir). Bu nedenle, zorunlu programlama, temelde değişkenlik ile bağdaşmaz.

Şimdi, OOP tarihsel olarak her zaman zorunlu programlama ile bağlantılı olmuştur (Simula Algol'a dayanmaktadır) ve tüm ana OOP dillerinin zorunlu kökleri vardır (C ++, Java, C #,… hepsi C'ye dayanmaktadır). Bu, OOP'un kendisinin zorunlu veya değişken olacağı anlamına gelmez, bu sadece OOP'nin bu diller tarafından uygulanmasının değişkenliğe izin verdiği anlamına gelir.


2
Özellikle iki temel özelliğin tanımı için çok teşekkür ederim.
Hyperboreus

Dinamik Satış, OOP'nin temel bir özelliği değildir. Gerçekten de Encapsulation (zaten kabul ettiğiniz gibi) değil.
Monica'ya Zarar Vermeyi Durdurun

5
@OrangeDog Evet, evrensel olarak kabul edilmiş bir OOP tanımı yok, ancak çalışacak bir tanımlamaya ihtiyacım vardı. Bu yüzden, bu konuda tam bir tez yazmadan alabileceğim gerçeğe yakın bir şey seçtim. Bununla birlikte, dinamik gönderimi, OOP'un diğer paradigmalardan tek önemli ayırt edici özelliği olarak görüyorum. OOP'a benzeyen ancak aslında tüm çağrıları statik olarak çözülmüş olan bir şey, geçici polimorfizm ile gerçekten sadece modüler bir programlamadır. Nesneler bir çift yöntem ve veridir ve kapanışlara eşdeğerdir.
amon

2
"Nesneler, metot ve veri eşleşmesidir" olur. İhtiyacınız olan tek şey değişmezlikle ilgisi olmayan bir şey.
Monica'ya Zarar Vermeyi Durdurun

3
@CodeYogi Veri gizleme en yaygın kapsülleme türüdür. Ancak, verilerin bir nesne tarafından dahili olarak depolanma şekli, gizlenmesi gereken tek uygulama detayı değildir. Ortak arabirimin nasıl uygulandığını gizlemek aynı derecede önemlidir, örneğin herhangi bir yardımcı yöntem kullanıp kullanmamam. Bu tür yardımcı yöntemler de genel olarak konuşursak, özel olmalıdır. Özetlemek gerekirse: kapsülleme bir ilkedir, oysa veri gizleme bir kapsülleme tekniğidir.
amon

25

Dikkat edin, nesnelerin çoğunun değişebilir olacağını OOP yapıyorsanız insanların OOP yaptığını varsaydığı, nesne odaklı programcılar arasında bir kültür var , ancak bu, OOP'nin değişkenlik gerektirip gerektirmediğinden ayrı bir konudur . Ayrıca bu kültür, insanların fonksiyonel programlamaya maruz kalmaları nedeniyle daha fazla değişkenliğe doğru yavaşça değişiyor gibi görünmektedir.

Scala, nesne yönelimi için değişkenlik gerekmediğine dair gerçekten iyi bir örnek. Scala değişkenliği desteklerken , kullanımı önerilmez. Deyimsel Scala nesne yönelimlidir ve neredeyse tamamen değişmezdir. Çoğunlukla Java ile uyumluluk için değişkenliğe izin verir ve çünkü bazı durumlarda değişmez nesneler çalışmak için yetersiz veya kıvrımlıdır.

Örneğin bir Scala listesini ve bir Java listesini karşılaştırın . Scala'nın değişmez listesi, Java'nın değiştirilebilir listesiyle aynı nesne yöntemlerini içerir. Dahası, aslında, Java sort gibi işlemler için statik işlevler kullandığından ve Scala gibi işlevsel stil yöntemleri de eklediğinden map. Tüm OOP belirtileri - kapsülleme, kalıtım ve polimorfizm - nesne yönelimli programcıların bildiği ve uygun şekilde kullanıldığı bir formda mevcuttur.

Göreceğiniz tek fark listeyi değiştirdiğinizde sonuç olarak yeni bir nesne almanızdır. Bu, genellikle değişken nesnelerden farklı tasarım desenleri kullanmanızı gerektirir, ancak OOP'yi tamamen bırakmanızı gerektirmez.


17

Taklit edilebilirlik, bir OOP dilinde, yalnızca nesne erişim noktalarını verileri mutasyona uğramayan yöntemler veya salt okunur özellikler olarak göstererek simüle edilebilir. Taklit edilebilirlik, bazı işlevsel dil özelliklerini kaçırmış olmanız dışında, herhangi bir işlevsel dilde olduğu gibi OOP dillerinde aynı şekilde çalışır.

Senin varsayımın, değişkenliğin nesne yöneliminin temel bir özelliği olduğu görünüyor. Ancak değişkenlik, basitçe nesnelerin veya değerlerin bir özelliğidir. Nesne yönelimi, mutasyonla ilgisi çok az olan veya hiç olmayan bir dizi içsel kavramı (kapsülleme, polimorfizm, kalıtım vb.) Kapsar ve her şeyi değişmez yapsanız bile, bu özelliklerin faydalarını elde edersiniz.

İşlevsel dillerin tümü de değişmezlik gerektirmez. Clojure, türlerin değişebilir olmasına izin veren özel bir ek açıklığa sahiptir ve "pratik" işlevsel dillerin çoğunun değişken türlerini belirtmek için bir yolu vardır.

Sorulması daha iyi bir soru olabilir: " Zorunlu değişkenlik, zorunlu programlamada anlam ifade ediyor mu?" Bu sorunun net cevabının hayır olduğunu söyleyebilirim. Zorunlu programlamada tam değişmezlik elde etmek için for, özyinelemenin lehine döngüler (bir döngü değişkenini mutasyona uğratmanız gerektiğinden) gibi şeylerden vazgeçmek zorunda kalacaksınız ve şimdi aslında zaten işlevsel bir şekilde programlama yapıyorsunuz.


Teşekkür ederim. Son paragrafınızı biraz detaylandırabilir misiniz ("açık" biraz öznel olabilir).
Hyperboreus

Zaten yaptım ....
Robert Harvey

1
@Hyperboreus Polimorfizm elde etmenin birçok yolu vardır . Dinamik gönderme, statik geçici polimorfizm (diğer bir deyişle aşırı fonksiyon yüklemesi) ve parametrik polimorfizm (aka jenerikler) ile alt tipleme, bunu yapmanın en yaygın yoludur ve tüm yolların güçlü ve zayıf yönleri vardır. Modern OOP dilleri bu üç yolu birleştirirken, Haskell öncelikle parametrik polimorfizm ve geçici polimorfizme dayanır.
amon

3
@RobertHarvey Değişime ihtiyacınız olduğunu söylüyorsunuz, çünkü döngü yapmanız gerekiyor (aksi halde özyineleme kullanmak zorundasınız). Haskell'i iki yıl önce kullanmaya başlamadan önce, değişken değişkenlere de ihtiyacım olduğunu düşündüm. Sadece "döngü" (harita, katlama, filtre vb.) İçin başka yollar olduğunu söylüyorum. Masadan bir kez döngü aldığınızda, başka değişken değişkenlere neden ihtiyaç duyacaksınız?
cimmanon

1
@RobertHarvey Ancak bu tam olarak programlama dillerinin amacıdır: Size neyin açığa çıktığını ve başlık altında ne olduğunu değil. İkincisi, uygulama geliştiriciden değil, derleyicinin veya tercümanın sorumluluğudur. Aksi takdirde montajcıya geri dönün.
Hyperboreus

5

Nesneleri enkapsüle eden değerler veya varlıklar olarak kategorize etmek genellikle yararlıdır; bunun bir farkı bir değer olması durumunda, kendisine referans veren bir kodun, durum değişikliğini hiçbir zaman kodun kendisi tarafından başlatılmadığını görmemesi gerektiğidir. Aksine, bir varlığa atıfta bulunan kod, referans sahibinin kontrolü dışındaki şekillerde değişmesini bekleyebilir.

Değişken veya değişmez türdeki nesneleri kullanarak kapsülleme değerini kullanmak mümkün olsa da, bir nesne ancak aşağıdaki koşullardan en az biri geçerli olduğunda değer olarak davranabilir:

  1. Nesneye yapılan atıf hiçbir zaman içinde kapsüllenen durumu değiştirebilecek hiçbir şeye maruz bırakılmayacaktır.

  2. Nesneye yapılan referanslardan en az birinin sahibi, mevcut herhangi bir referansın kullanabileceği tüm kullanımları bilir.

Değişmez türlerin tüm örnekleri ilk gereksinimi otomatik olarak karşıladığından, değerleri değer olarak kullanmak kolaydır. Değişken tipler kullanılırken her iki gereksinimin de karşılandığından emin olmak, aksine, çok daha zordur. Değişmez türlere yapılan referanslar, içinde kapsüllenmiş durumu kapsülleme aracı olarak serbestçe dolaşırken, değişebilen türlerde saklanan durumun etrafından geçmek, ya değişmeyen sarıcı nesnelerin oluşturulmasını ya da özel olarak tutulan nesneler tarafından kapsanan durumun diğer nesnelere kopyalanmasını gerektirir. Verinin alıcısı tarafından sağlanmış veya yapılmış.

Değiştirilemez türler değerleri geçmek için çok iyi çalışır ve genellikle bunları değiştirmek için en azından bir şekilde kullanılabilir. Ancak, varlıkları idare etmede çok iyi değillerdir. Tamamen değişmez türlere sahip bir sistemde bir varlığa sahip olabileceği en yakın şey, sistemin durumu göz önüne alındığında, bir kısmının özniteliklerini bildirecek veya benzeri bir sistem durumu örneği üreten bir işlevdir. bazı seçilebilir şekillerde farklı olacak bazı özel kısımları dışında bir tedarik edilmiştir. Ayrıca, bir işletmenin amacı, gerçek dünyada var olan bir şeye bazı kodlar bağlamaksa, işletmenin değişebilir durumu göstermekten kaçınması imkansız olabilir.

Örneğin, eğer bir TCP bağlantısı üzerinden bir veri alırsa, o kişi eski "dünyanın durumu" na referansı etkilemeden, bu verileri arabelleğine içeren yeni bir "dünyanın durumu" nesnesi üretebilir Son veri grubunu içermeyen dünya devleti arızalı olacak ve artık gerçek dünya TCP soketinin durumuyla eşleşmeyecekleri için kullanılmamalıdır.


4

C # 'da bazı türler string gibi değişmezdir.

Bu, seçimin şiddetle değerlendirildiğini ileri sürüyor gibi görünüyor.

Elbette, bu tür yüzbinlerce kez değiştirmek zorunda kalırsanız, değişmez türler kullanmak gerçekten zor bir performans. Bu durumda , StringBuildersınıf yerine sınıf kullanılması önerilmesinin nedeni budur string.

Bir profilci ile bir deney yaptım ve değişmez tipini kullanmak gerçekten daha fazla işlemci ve RAM gerektiriyor.

Ayrıca, 4000 karakterlik bir dizgede yalnızca bir harfi değiştirmek için RAM'in başka bir alanındaki her bir karakteri kopyalamanız gerektiğini düşünmeniz sezgiseldir.


6
Değişmez verileri sıklıkla değiştirmek, tekrarlanan stringbirleştirme ile olduğu gibi felaketsel olarak yavaş olmak zorunda değildir . Hemen hemen her türlü veri / kullanım durumu için, verimli bir kalıcı yapı (çoğu zaman zaten var) icat edilebilir. Sabit faktörler bazen daha kötü olsa bile, bunların çoğu kabaca eşit performansa sahiptir.

@delnan Ayrıca cevabın son paragrafının, uygulama imgesi hakkında (im) değişkenlikten daha fazla olduğunu düşünüyorum.
Hyperboreus

@Hyperboreus: Bu bölümü silmem gerektiğini düşünüyor musunuz? Fakat bir string değişmez ise nasıl değişebilir? Demek istediğim .. bence, ama elbette yanılıyor olabilirim, nesnenin değişmez olmasının temel nedeni bu olabilir.
nceki

1
@Revious Hiçbir şekilde. Bırak onu, tartışmaya ve daha ilginç görüşlere ve bakış açılarına neden olur.
Hyperboreus

1
@Seçenekli Evet, okuma, yavaştır string(a. Geleneksel gösterimi). 1000 değişiklikten sonra (bahsettiğim gösterimde) bir "string" sadece yeni yaratılmış bir string (modulo içeriği) gibidir; yararlı veya yaygın olarak kullanılan kalıcı veri yapısı, X işlemlerinden sonra kaliteyi bozmaz. Bellek parçalanması ciddi bir sorun değil (birçok tahsisatınız var, evet, ancak parçalanma modern çöp toplayıcılarında sorun değil)

0

Her şeyin tamamen değişmezliği, çok büyük bir nedenden ötürü OOP'ta veya bu konudaki diğer paradigmalarda pek bir anlam ifade etmiyor:

Her faydalı programın yan etkileri vardır.

Hiçbir şeyin değişmesine neden olmayan bir program değersizdir. Etkisi aynı olacağından, onu bile çalıştırmamış olabilirsiniz.

Hiçbir şeyi değiştirmeyeceğinizi ve bir şekilde aldığınız sayıların bir listesini topladığınızı düşünüyor olsanız bile, sonuçla bir şey yapmanız gerektiğini düşünün - standart çıktıya yazdırıp yazdırmamaya, bir dosyaya yaz, ya da her yerde. Bu da tamponun mutasyona uğramasını ve sistemin durumunu değiştirmesini içerir.

Değişebilir olması gereken parçalarla değişkenliği sınırlandırmak çok mantıklı olabilir. Ama kesinlikle hiçbir şeyin değişmesi gerekmiyorsa, yapmaya değer bir şey yapmazsınız.


4
Saf işlevsel dilleri ele almadığım için cevabınızın soru ile nasıl ilişkili olduğunu göremiyorum. Örneğin, erlang'ı ele alalım: Değiştirilemez veriler, yıkıcı görev yok, yan etkiler konusunda bir sorun yok. Ayrıca, işlevsel bir dilde durumunuz vardır, yalnızca durumun üzerinde çalışan işlevlerin aksine, işlevler arasında "akan" durum vardır. Devlet değişiyor, ancak yer değiştirmiyor, ancak gelecekteki bir devlet mevcut durumun yerini alıyor. İmkansızlık, bir bellek tamponunun değişip değişmediği ile ilgili değildir, bu mutasyonların dışarıdan görülebilmesi ile ilgilidir.
Hyperboreus

Ve gelecekteki bir devlet mevcut olanı değiştirir nasıl tam olarak? Bir OO programında, bu durum bir yerdeki bir nesnenin özelliğidir. Durumun değiştirilmesi, bir nesneyi değiştirmeyi (veya onu başka biriyle değiştirmeyi gerektirir; bu, kendisine başvuran nesnelerde değişiklik yapılmasını gerektirir (veya onu başka biriyle değiştirmeyi gerektirir, ki… noktayı anlarsınız). Her hareketin tamamen yeni bir uygulama yaratmasıyla sonuçlanan bir tür monadik kesmekle karşılaşabilirsiniz ... ancak o zaman bile, programın mevcut durumu bir yere kaydedilmelidir.
cHao

7
-1. Bu yanlış. Yan etkileri mutasyonla karıştırıyorsunuz ve aynı şekilde fonksiyonel diller tarafından aynı şekilde tedavi edilirken, farklılar. Her faydalı programın yan etkileri vardır; Her faydalı programın mutasyonu yoktur.
Michael Shaw,

@Michael: OOP'a gelince, mutasyon ve yan etkiler öylesine iç içe geçerler ki, gerçekçi bir şekilde ayrılamazlar. Mutasyonunuz yoksa, büyük miktarlarda korsanlık olmadan yan etkilere sahip olamazsınız.
cHao

Ve yine de, bu soruya
yorumunda

-2

OOP tanımınızın bir mesaj iletme stili kullanıp kullanmadığına bağlı olduğunu düşünüyorum.

Saf fonksiyonların hiçbir şeyi değiştirmesi gerekmez, çünkü yeni değişkenlerde saklayabileceğiniz değerleri döndürürler.

var brandNewVariable = pureFunction(foo);

İleti aktarma stiliyle, bir nesneye, yeni bir değişkende hangi yeni verileri saklamanız gerektiğini sormak yerine, yeni verileri saklamasını söylersiniz.

sameOldObject.changeMe(foo);

Nesnelerine sahip olmak ve onları mutasyona uğratmamak, yöntemlerini dışardan ziyade nesnenin içinde yaşayan saf fonksiyonlar yaparak mümkündür.

var brandNewVariable = nonMutatingObject.askMe(foo);

Ancak mesaj geçme stilini ve değişmez nesneleri karıştırmak mümkün değildir.

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.