“Fonksiyonlar ve veriler arasında sıkı bağlantı” neden kötü?


38

Bu alıntıyı " Clojure'nin Sevinci" sinde p. 32, ancak birileri geçen hafta akşam yemeğinde bana aynı şeyi söyledi ve ben de başka yerlerde duydum:

Nesne yönelimli programlamanın dezavantajı [A], fonksiyon ve veri arasındaki sıkı bağlantıdır.

Bir uygulamada gereksiz eşlemenin neden kötü olduğunu anlıyorum. Ayrıca değişken durum ve kalıtımın, Nesneye Yönelik Programlama'da bile, kaçınılması gerektiğini söyleyerek rahatım. Ancak sınıflara yapıştırma işlevlerinin neden doğal olarak kötü olduğunu göremiyorum.

Yani, bir sınıfa işlev eklemek Gmail'de bir posta etiketlemek veya bir dosyayı bir klasöre yapıştırmak gibi görünüyor. Onu tekrar bulmanıza yardımcı olan örgütsel bir tekniktir. Bazı kriterleri seçiyorsun, sonra bir şeyler yap. OOP'tan önce, programlarımız dosyalarda oldukça büyük yöntem torbalarıydı. Yani, işlevleri bir yere koymak zorundasın. Neden onları organize etmiyorsun?

Bu, türlere örtülü bir saldırı ise, neden giriş ve çıkış türlerinin bir işlevle sınırlandırılmasının yanlış olduğunu söylemiyorlar? Buna katılıp katılamayacağımı bilmiyorum, ama en azından pro ve con tipi güvenliği tartışıyorlar. Bu bana çoğunlukla ayrı bir endişe gibi geliyor.

Elbette, bazen insanlar bunu yanlış anlar ve işlevselliği yanlış sınıfa koyar. Ancak diğer hatalarla karşılaştırıldığında, bu çok küçük bir rahatsızlık gibi görünüyor.

Öyleyse, Clojure'un ad alanları var. OOP'taki bir sınıfa bir işlevin Clojure'da bir isim alanına işlevin yapıştırılmasından farklı olması ve neden bu kadar kötü? Unutmayın, bir sınıftaki işlevler mutlaka yalnızca o sınıfın üyeleri üzerinde işlemez. Java.lang.StringBuilder'a bakın - herhangi bir referans tipinde veya otomatik boks yoluyla herhangi bir tipte çalışır.

Not: Bu alıntı okumadığım bir kitaba atıfta bulunuyor: Leda'da Multiparadigm Programlama: Timothy Budd, 1995 .


20
Yazarın OOP'yi doğru anlamadığını ve Java'nın kötü olduğunu ve Clojure'un iyi olduğunu söylemek için bir nedene daha ihtiyacı olduğunu düşünüyorum. / rant
Euphoric

6
Örnek yöntemleri (serbest işlevlerin veya uzantı yöntemlerinin aksine) diğer modüllerden eklenemez. Yalnızca örnek yöntemleriyle uygulanabilecek arayüzleri göz önüne aldığınızda, bu bir kısıtlama haline gelir. Farklı modüllerde bir arabirim ve bir sınıf tanımlayamaz ve sonra bunları bir araya getirmek için üçüncü bir modülün kodunu kullanabilirsiniz. Daha esnek bir yaklaşım, haskell tipi sınıflar gibi bunu yapabilmelidir.
CodesInChaos

4
Ben yazar inanıyoruz @Euphoric vermedi anlıyorum ama Clojure topluluk OOP bir saman adam yapmak ister gibi görünüyor ve biz iyi çöp toplama, büyük miktarda bellek, hızlı işlemciler vardı önce programlama bütün kötülüklerin bir büst olarak yakmak ve çok fazla disk alanı. Örneğin, OOP'u yenmeyi bırakıp gerçek nedenleri hedeflemelerini isterdim: örneğin, Neuman mimarisi.
GlenPeterson

4
Benim izlenimim, OOP eleştirisinin çoğunun gerçekte Java'da uygulanan OOP eleştirisi olduğu yönünde. Bu kasıtlı bir saman adamı olduğu için değil, ama OOP ile ilişkilendirdikleri şey. Statik yazarak şikayetçi olan insanlar ile oldukça benzer sorunlar var. Meselelerin çoğu konsepte bağlı değil, sadece bu kavramın popüler bir şekilde uygulanmasındaki kusurlu.
KodlarInChaos

3
Başlığınız, sorunuzun gövdesine uymuyor. İşlevlerin ve verilerin sıkı bağlanmasının neden kötü olduğunu açıklamak kolaydır, ancak metniniz "OOP bunu yapıyor mu?", "Öyleyse neden?" ve "Bu kötü bir şey mi?". Şimdiye kadar, bu üç sorudan bir veya daha fazlasıyla ilgilenen cevapları alacak kadar şanslıydınız ve hiçbiri daha basit bir soru olarak kabul etmediniz.
Eylül’de

Yanıtlar:


34

Teoride, gevşek fonksiyon-veri birleştirme aynı veri üzerinde çalışmak için daha fazla fonksiyon eklemeyi kolaylaştırır. Aşağı tarafı, veri yapısını değiştirmeyi zorlaştırmaktadır, bu nedenle pratikte iyi tasarlanmış işlevsel kod ve iyi tasarlanmış OOP kodunun çok benzer bağlanma seviyelerine sahip olmaları gerekir.

Örnek veri yapısı olarak yönlendirilmiş bir asiklik grafik (DAG) alın. İşlevsel programlamada, kendini tekrarlamaktan kaçınmak için hala bir soyutlamaya ihtiyaç duyarsınız, böylece düğüm ve kenarları ekleme ve silme, belirli bir düğümden erişilebilen düğümleri bulma, topolojik sıralama oluşturma, vb. İşlevler içeren bir modül yapacaksınız. derleyici tarafından zorlanmadığı halde verilere etkili bir şekilde sıkı sıkıya bağlıdırlar. Bir düğümü zor yoldan ekleyebilirsiniz, ama neden istemesin ki? Bir modüldeki yapışkanlık, sistem genelinde sıkı bağlantıyı önler.

Tersine, OOP tarafında, temel DAG işlemleri dışındaki tüm işlevler, ayrı bir "görünüm" sınıfında, DAG nesnesi parametre olarak geçirildiğinde gerçekleştirilecektir. DAG verileri üzerinde çalışan, işlevsel programda bulacağınız aynı düzeyde işlev verisi ayrıştırma düzeyini yaratarak istediğiniz kadar görünüm eklemek kolaydır. Derleyici, her şeyi tek bir sınıfa sıkıştırmaktan alıkoymayacak, ancak meslektaşlarınız olacak.

Programlama paradigmalarını değiştirmek en iyi soyutlama, uyum ve eşleştirme uygulamalarını değiştirmez, sadece derleyiciyi uygulamanıza yardımcı olan uygulamaları değiştirir. İşlevsel programlamada, işlev-veri birleşimi istediğinizde, derleyici yerine beyler sözleşmesi ile uygulanır. OOP'de model-görünüm ayrımı, derleyici yerine beyler sözleşmesiyle uygulanır.


13

Bilmiyorsanız, bu görüşü zaten almalısınız: Nesne yönelimli ve kapanış kavramları aynı madalyonun iki yüzüdür. Yani, kapanış nedir? Değişkenleri veya verileri çevreleyen kapsamdan alır ve işlevin içine bağlar ya da bir OO perspektifinden, örneğin bir şeyi bir kurucuya iletirken aynı şeyi etkili bir şekilde yaparsınız; bu örneğin bir üye işlevindeki veri parçası. Ancak işleri çevreleyen kapsamdan almak iyi bir şey değildir - çevreleyen kapsam ne kadar büyükse, bunu yapmak o kadar kötüdür (pragmatik olarak, işlerin yapılması için genellikle bazı kötülükler gerekir). Küresel değişkenlerin kullanımı, bunu, bir programdaki işlevlerin program kapsamındaki değişkenleri kullandığı aşırı noktaya götürüyor - gerçekten çok kötü. VarKüresel değişkenlerin neden kötü olduğu konusunda başka yerlerde iyi açıklamalar .

Eğer OO tekniklerini takip ediyorsanız, temelde programınızdaki her bir modülün belli bir minimum kötülük seviyesine sahip olacağını kabul etmiş olursunuz. Programlamaya işlevsel bir yaklaşım izlerseniz, programınızdaki hiçbir modülün kapanma kötülüğü içermeyeceği, hala bazılarınız olmasına rağmen, OO'dan çok daha az olacağı bir ideali hedefliyorsunuz.

Bu, OO'nun dezavantajıdır - bu tür bir kötülüğü teşvik eder, veriyi kapama standartlarını (bir çeşit kırık pencere programlama teorisi gibi) yaparak işlevini yerine getirir .

Tek artı tarafı, başlamak için çok sayıda kapak kullanacağınızı biliyorsanız, OO en azından ortalama bir programcının anlayabilmesi için bu yaklaşımı düzenlemenize yardımcı olacak fikirsel bir çerçeve sunar. Özellikle kapatılmakta olan değişkenler, sadece bir işlev kapatmasında dolaylı olarak ele alınmak yerine kurucuda açıktır. Çok fazla kapama kullanan fonksiyonel programlar, genellikle daha az zarif olmasa da, eşdeğer OO programından daha fazla şifrelidir :)


8
Günün Alıntı: "işi yapmak için genellikle bazı kötülük gereklidir"
GlenPeterson

5
Neden kötülük dediğiniz şeylerin şeytan olduğunu açıklamıyorsunuz ; Sen sadece onlara kötülük diyorsun. Neden şeytan olduklarını açıklayın ve centilmenlerin sorusuna bir cevap olabilir.
Robert Harvey

2
Son paragrafınız ise cevabı kaydeder. Size göre, tek artı tarafı olabilir, ama bu küçük bir şey değil. "Ortalama programcılar" olarak adlandırdığımız bizler, gerçekten neler olduğunu bize bildirmek için yeterli olan belirli bir tören miktarını memnuniyetle karşılıyoruz.
Robert Harvey

OO ve kapanışlar eşanlamlıysa, neden bu kadar çok OO dili onlara açıkça destek sağlayamadı? Aldığın C2 wiki sayfası o site için normal olandan daha fazla tartışmaya (ve daha az fikir birliğine) sahip.
saat

1
@ itsbruce Onlar büyük ölçüde gereksiz yapılır. "Kapatılacak" değişkenler, nesneye geçirilen sınıf değişkenleri haline gelir.
Izkata

7

Bu tür kaplin hakkında:

Bu nesne üzerinde çalışmak üzere bir nesneye yerleşik bir işlev, diğer nesne türlerinde kullanılamaz.

Haskell'de, tip sınıflarına karşı çalışmak için fonksiyonlar yazıyorsunuz - bu nedenle, herhangi bir fonksiyonun üzerinde çalışabileceği , fonksiyonun üzerinde çalıştığı belirli bir sınıfın türü olduğu sürece, üzerinde çalışabileceğiniz birçok farklı türde nesne vardır .

Serbest duran fonksiyonlar, fonksiyonlarınızı A tipinde çalışmak üzere yazmaya odaklandığınızda elde edemediğiniz dekuplajlara izin verir, çünkü fonksiyonun bir A tipi örneğine sahip değilseniz kullanamazsınız. Aksi halde B tipi bir vakada veya C tipi vakada kullanılacak kadar genel olabilir.


3
Arayüzün bütün noktası bu değil mi? B ve C tiplerinin fonksiyonunuzla aynı görünmesine izin veren şeyleri sağlamak için birden fazla tipte çalışabilir mi?
Random832 25:13

2
@ Random832 kesinlikle, ancak o veri tipiyle çalışmamak için neden bir veri tipinin içine bir fonksiyon katıştırın? Cevap: O tek sebebi için bir veri türü gömmek bir fonksiyonu. Statik sınıflardan başka hiçbir şey yazamaz ve tüm işlevlerinizi , kapsandıkları veri türüne aldırış etmemelerini, kendi türlerinden tamamen ayrılmalarını sağlayamazsınız , ancak o zaman neden onları bir türe koyma zahmetine katlanırsınız? İşlevsel yaklaşım diyor ki: Rahatsız etmeyin, işlevlerinizi bir çeşit ara yüzlere doğru çalışmak için yazın ve sonra bunları verilerinizle kapsüllemek için hiçbir neden yoktur.
Jimmy Hoffa

Hala arayüzleri uygulamanız gerekiyor.
Random832

2
@ Random832 arayüzleri veri tipleridir; içlerinde kapsüllenmiş bir fonksiyona ihtiyaç duymazlar. Ücretsiz işlevlerle, tüm arabirimlerin keşfedilmesi gereken, işlevlerin çalışması için hangi verileri sağladıklarıdır.
Jimmy Hoffa,

2
@ Random832, OO'da olduğu gibi gerçek dünyadaki nesnelerle bağlantı kurmak için bir kitabın arayüzünü düşünün: Bilgi (veri) sunar, hepsi bu. Sayfaları olan türlerin sınıfına karşı çalışan, sayfanın her tür kitaplarına, gazete kâğıtlarına, K-Mart'ta bu poster iğlerine, tebrik kartlarına, postaya, köşe. Dönüş sayfasını kitabın bir üyesi olarak uyguladıysanız, dönüş sayfasını kullanabildiğiniz her şeyi kaçırırsınız, ücretsiz bir işlev olarak bağlı değildir; sadece biraya bir PartyFoulException fırlatıyor.
Jimmy Hoffa

4

Java ve OOP benzeri enkarnasyonlarında, örnek yöntemler (serbest işlevler veya uzatma yöntemlerinin aksine) diğer modüllerden eklenemez.

Yalnızca örnek yöntemleriyle uygulanabilecek arabirimleri düşündüğünüzde, bu bir kısıtlama haline gelir. Farklı modüllerde bir arabirim ve bir sınıf tanımlayamaz ve sonra bunları bir araya getirmek için üçüncü bir modülün kodunu kullanabilirsiniz. Haskell'in tipi sınıfları gibi daha esnek bir yaklaşım bunu yapabilmelidir.


Bunu Scala'da kolayca yapabilirsiniz. Go ile aşina değilim, ama AFAIK orada da yapabilirsiniz. Ruby'de nesnelere, bazı arayüzlere uyum göstermelerini sağladıktan sonra yöntemler eklemek oldukça yaygın bir uygulamadır. Tanımladığınız şey, kötü bir şekilde tasarlanmış tipte bir sistem gibi görünüyor, hatta OO ile uzaktan bile ilgili bir şey değil. Düşünce deneyi gibi: Nesneler yerine Soyut Veri Türleri hakkında konuşurken cevabınız nasıl farklı olurdu? Herhangi bir fark yaratacağına inanmıyorum, bu da argümanının OO ile ilgisi olmadığını kanıtlayacak.
Jörg W Mittag

1
@ JörgWMittag Cebirsel veri türleri demek istediğini düşünüyorum. Ve Kodlar InChaos, Haskell açıkça önerdiğiniz şeyi reddetmektedir. Buna yetimlik örneği denir ve GHC ile ilgili uyarılar yayınlanır.
jozefg

3
@ JörgWMittag Benim izlenimim, OOP'yi eleştirenlerin çoğu, Java ve benzeri dillerde kullanılan katı OOP biçimini katı sınıf yapısıyla eleştiriyor ve örnek yöntemlere odaklanıyor. Bu alıntıya dair izlenimim, örnek yöntemlere odaklanmayı eleştirdiği ve golang'ın kullandığı gibi OOP'un diğer lezzetleri için geçerli olmadığıdır.
KodlarInChaos

2
@CodesInChaos O zaman belki bunu "statik sınıf temelli OO" olarak açıklığa kavuşturma
jozefg

@jozefg: Soyut Veri Tiplerinden bahsediyorum. Cebirsel Veri Tiplerinin bu tartışma ile uzaktan nasıl ilgili olduğunu bile anlamıyorum.
Jörg W Mittag

3

Nesne Oryantasyonu temel olarak Prosedürel Veri Soyutlama ile ilgilidir (veya dikey bir sorun olan yan etkileri ortadan kaldırırsanız, İşlevsel Veri Soyutlama). Bir anlamda, Lambda Matematik, en eski ve en saf Nesne Yönelimli dildir, çünkü yalnızca İşlevsel Veri Özeti sağlar (çünkü işlevlerin dışında herhangi bir yapıya sahip değildir).

Yalnızca tek bir nesnenin işlemleri, o nesnenin veri temsilini denetleyebilir. Aynı tipteki diğer nesneler bile bunu yapamaz. (Nesne Yönelimli Veri Soyutlama ve Soyut Veri Tipleri arasındaki temel fark budur: ADT'ler ile aynı tip nesneler birbirlerinin veri temsilini inceleyebilir, yalnızca diğer tiplerdeki nesnelerin gösterimi gizlenir.)

Bunun anlamı, aynı tipteki birkaç nesnenin farklı veri gösterimlerine sahip olabileceğidir. Aynı nesnenin bile farklı zamanlarda farklı veri gösterimleri olabilir. (Örneğin, Scala'da Maps ve Sets, eleman sayısına bağlı olarak bir dizi ile bir karma değer arasında geçiş yapar, çünkü çok küçük sayılar için bir dizideki doğrusal arama çok küçük sabit faktörler nedeniyle bir arama ağacındaki logaritmik aramadan daha hızlıdır. .)

Bir nesnenin Dışarıdan, sen, olmamalı olamaz , veri temsilini biliyoruz. Bu sıkı kaplin karşıtıdır .


OOP'da koşullara bağlı olarak iç veri yapılarını değiştiren sınıflarım var, bu yüzden bu sınıfların nesne örnekleri aynı anda çok farklı veri gösterimleri kullanıyor olabilir. Temel veri gizleme ve kapsülleme diyebilir miyim? Öyleyse Scala'daki Harita düzgün uygulanmış (veri gizleme ve enkapsülasyon) Harita sınıfından bir OOP dilinde nasıl farklıdır?
Marjan Venema

Örneğinizde, verilerinizi bir sınıftaki erişimci işlevleriyle kapsüllemek (ve böylece bu işlevleri bu verilere sıkıca bağlamak) aslında, o sınıfın örneklerini programınızın geri kalanıyla birlikte gevşek bir şekilde birleştirmenize olanak tanır. Teklifin merkezi noktasını reddediyorsunuz - çok hoş!
GlenPeterson

2

Veri ve fonksiyonlar arasındaki sıkı bağlantı, çünkü her birini diğerinden bağımsız olarak değiştirebilmeniz ve sıkı bağlantı, bunu zorlaştırır, çünkü birisini diğerine habersiz ve muhtemelen değişmeden değiştiremezsiniz.

İşlevde sunulan farklı verilerin, işlevde herhangi bir değişiklik gerektirmesini istemez ve benzer şekilde, bu işlev değişikliklerini desteklemek için üzerinde çalıştığı verilerde herhangi bir değişiklik yapmadan, işlevde değişiklik yapabilmek istersiniz.


1
Evet onu istiyorum. Ancak benim deneyimim, önemsiz olmayan bir işleve, açıkça işlenecek şekilde tasarlanmadığı verileri gönderdiğinizde, bu işlevin bozulma eğiliminde olduğudur. Ben sadece güvenlik türünden değil, fonksiyonun yazarı (ları) tarafından beklenmeyen herhangi bir veri koşulundan bahsetmiyorum. İşlev eskiyse ve sık sık kullanılıyorsa, yeni verinin içinden geçmesine izin veren herhangi bir değişikliğin, hala çalışması gereken eski bir veri biçimi için onu kırması olasıdır. Dekuplaj, fonksiyonlar ve veriler için ideal olsa da, dekuplajın gerçekliği zor ve tehlikeli olabilir.
GlenPeterson
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.