Sadece tek (genel) bir metoda sahip sınıflar bir problem midir?


51

Şu anda video izleme görüntülerinde sıkıştırma ve indeksleme yapan bir yazılım projesi üzerinde çalışıyorum. Sıkıştırma, arka plan ve ön plan nesnelerini bölerek, ardından arka planı statik bir görüntü olarak ve ön planı bir hareketli grafik olarak kaydederek çalışır.

Son zamanlarda, proje için tasarladığım bazı dersleri incelemeye başladım.

Sadece tek bir kamu yöntemine sahip birçok sınıf olduğunu fark ettim. Bu sınıflardan bazıları:

  • VideoCompressor (türünde compressbir giriş videosu alan ve türünde RawVideobir çıkış videosu döndüren bir yöntemle CompressedVideo).
  • VideoSplitter (türünde splitbir giriş videosu alan RawVideove her biri türünde 2 çıkış videosu vektörü döndüren bir yöntemle RawVideo).
  • VideoIndexer (türünde indexbir giriş videosu alan ve türünde RawVideobir video dizini döndüren bir yöntemle VideoIndex).

Kendimi gibi arama yapmak için her sınıf başlatmasını bulmak VideoCompressor.compress(...), VideoSplitter.split(...), VideoIndexer.index(...).

Yüzeyde, sınıf adlarının amaçlanan fonksiyonlarını yeterince açıklayıcı olduğunu düşünüyorum ve aslında isimlerdir. Buna bağlı olarak, onların yöntemleri de fiiller.

Bu gerçekten bir sorun mu?


14
Dile bağlıdır. C ++ veya Python gibi çok paradigma bir dilde bu sınıfların işi yoktur: Onların "yöntemleri" serbest fonksiyonlar olmalıdır.

3
@delnan: C ++ 'ta bile, tam "OO yeteneklerine" ihtiyaç duymasanız bile, modüller oluşturmak için genellikle sınıfları kullanırsınız. Aslında, "boş işlevleri" bir modülde birlikte gruplamak yerine ad alanlarını kullanmanın bir alternatifi var, ancak bunun içinde hiçbir avantaj göremiyorum.
Doktor Brown,

5
@DocBrown: C ++ bunun için isim alanlarına sahip. Aslında, modern C ++ 'da metotlar için genellikle ücretsiz fonksiyonlar kullanırsınız, çünkü metotlar tüm savcılar için (statik olarak) aşırı yüklenebilirken, metotlar sadece istilacı için aşırı yüklenebilir.
Jan Hudec

2
@DocBrown: Bu sorunun çok çekirdeğidir. Yalnızca bir "dış" yöntem olan ad alanlarını kullanan modüller, işlev yeterince karmaşık olduğunda tamamen iyidir. Çünkü ad alanları hiçbir şeyi temsil etmiyormuş gibi davranmıyor ve nesne yönelimlimiş gibi davranmıyor. Sınıflar yapar, ancak bunun gibi sınıflar gerçekten sadece ad alanlarıdır. Elbette Java'da bir işleve sahip olamazsınız, bu yüzden sonuç budur. Java ile ilgili ayıp.
Jan Hudec

2
İlgili (neredeyse bir kopya): programmers.stackexchange.com/q/175070/33843
Heinzi,

Yanıtlar:


87

Hayır, bu tam tersi bir problem değil. Modülerliğin ve sınıfın açık sorumluluğunun bir işaretidir. Yalın arabirimin, bu sınıftaki bir kullanıcının bakış açısından anlaşılması kolaydır ve gevşek bağlantıyı teşvik edecektir. Bunun birçok avantajı var ama neredeyse hiçbir sakıncası yok. Keşke daha fazla bileşen bu şekilde tasarlansaydı!


6
Bu doğru cevap ve ben bunu reddettim, ancak bazı avantajlarını açıklayarak daha iyi hale getirebilirsiniz. OP'nin içgüdüsünün ona bir sorun olduğunu söylediği şeyin başka bir şey olduğunu düşünüyorum ... yani VideoCompressor'ın gerçekten bir arayüz olduğunu. Mp4VideoCompressor bir sınıftır ve bir VideoSplitter kodunu yeni bir sınıfa kopyalamaksızın başka bir VideoCompressor ile değiştirilebilir olmalıdır.
pdr

1
Üzgünüm ama yanılıyorsun. Tek bir metodu olan bir sınıf sadece bağımsız bir fonksiyon olmalıdır.
Miles Rout,

3
@MilesRout: yorumunuz, soruyu yanlış anladığınızı gösteriyor - OP, tek bir yöntemle değil, bir genel yöntemle bir sınıf hakkında yazdı (ve aslında burada bize tek bir yorumda söylediği gibi, çok özel yöntemlerle bir sınıf anlamına geliyordu).
Doktor Brown

2
“Tek bir metodu olan bir sınıf sadece bağımsız bir fonksiyon olmalıdır.” Bu yöntemin ne olduğu hakkında brüt bir varsayım. Bir genel yöntemle bir sınıfım var. BEHIND IT, bu sınıftaki çoklu yöntemlerin yanı sıra, kesinlikle müşteri hakkında hiçbir fikri olmayan 5 diğer sınıftır. Mükemmel SRP uygulaması, katmanlı sınıf yapısının temiz, basit bir arayüzünü sağlar, bu da sadeliği en üste çıkarır.
radarbob

2
@Andy: soru "bu bir problem" idi ve "bu belirli bir OO tanımına uymuyor" değil. Ayrıca, OP'nin devletsiz sınıflar anlamına geldiği sorusunda kesinlikle bir işaret yoktur.
Doc Brown,

26

Artık nesne yönelimli değil. Bu sınıflar hiçbir şeyi temsil etmediğinden, sadece işlevler için gemilerdir.

Bu yanlış olduğu anlamına gelmez. İşlevsellik yeterince karmaşıksa veya genelse (yani, argümanlar somut son türler değil, arabirimlerdir), bu işlevi ayrı bir modüle koymak mantıklı olur .

Oradan diline bağlı. Dil serbest fonksiyonlara sahipse, fonksiyonları veren modüller olmalıdır. Neden böyle bir sınıf olmadığını iddia ediyorsun. Dil, örneğin Java gibi ücretsiz fonksiyonlara sahip değilse, o zaman tek ortak yöntemle sınıflar yaratırsınız. Bu sadece nesne yönelimli tasarımın sınırlarını gösterir. Bazen işlevsel, sadece daha iyi bir eşleşmedir.

Tek genel yöntemle bir sınıfa ihtiyaç duyabileceğiniz bir durum vardır, çünkü tek genel yöntemle arayüz uygulamak zorundadır. Gözlemci modeli veya bağımlılık enjeksiyonu için ya da her neyse. Burada yine dile bağlı. Birinci sınıf fonksiyonlara sahip dillerde (C ++ ( std::functionveya template parametresi), C # (delege), Python, Perl, Ruby ( proc), Lisp, Haskell, ...) bu modeller fonksiyon tiplerini kullanır ve sınıflara ihtiyaç duymaz. Java (henüz sürüm 8'de bulunmayacak) fonksiyon tiplerine sahip olmadığı için, tek metotlu arayüzleri ve ilgili metot sınıflarını kullanırsınız.

Tabii ki tek bir büyük fonksiyon yazmayı savunmuyorum. Özel alt yordamları olmalıdır, ancak uygulama dosyasına özel olabilir (C + 'da dosya düzeyinde statik veya adsız ad alanı) veya yalnızca genel işlev içinde başlatılmış özel bir yardımcı sınıfta ( Veri depolamak veya değil ).


12
"Neden böyle bir sınıf olmadığını iddia ediyorsun." Serbest işlev yerine bir nesneye sahip olmak, duruma ve alt tiplemeye izin verir. Bu, gereksinimlerin değiştiği bir dünyada değerli olabilir. Er ya da geç, video sıkıştırma ayarlarıyla oynamaya veya alternatif sıkıştırma algoritmaları sağlama ihtiyacı vardır. Ondan önce "sınıf" kelimesi bize bu fonksiyonun açık bir sorumlulukla kolayca genişletilebilir ve değiştirilebilir bir yazılım modülüne ait olduğunu söyler. OO'nun esas amacı bu değil midir?
GELMEKTEDİR

1
Peki, yalnızca bir kamu yöntemi (ve korumalı olanları ima etme türü) varsa, gerçekten genişletilemez. Elbette, paketleme sıkıştırma parametreleri, bu durumda işlev nesnesi haline geldiğinde mantıklı olabilir (bazı diller, bazıları için ayrı bir desteğe sahiptir, bazıları yoktur).
Jan Hudec

3
Bu, nesneler ve işlevler arasındaki temel bir bağlantıdır: işlev, tek bir yöntemle ve alan içermeyen bir nesneye izomorfiktir. Bir kapatma, tek bir yöntem ve bazı alanlar içeren bir nesneye izomorfiktir. Aynı değişken setine yaklaşan birkaç fonksiyondan birini döndüren bir seçici fonksiyon, bir nesneye izomorfiktir. (Aslında bu bir selektör işlevi yerine bir sözlük veri yapısını kullanmak dışında nesneler, JavaScript kodlanıyor nasıl.)
Jörg W Mittag

4
“Artık nesne yönelimli değil. Bu sınıflar hiçbir şeyi temsil etmediği için sadece işlevler için kaplar.” - Bu yanlış. Bu yaklaşımın DAHA FAZLA nesne yönelimli olduğunu iddia ediyorum. OO, gerçek dünyadaki bir nesneyi temsil ettiği anlamına gelmez. Büyük miktarda programlama soyut kavramlarla ilgilidir, ancak bu, çözmek için bir OO yaklaşımı uygulayamayacağınız anlamına mı geliyor? Kesinlikle hayır. Modülerlik ve soyutlama hakkında daha fazla. Her nesnenin tek bir sorumluluğu vardır. Bu, başınızı programın etrafına sarmayı ve değişiklik yapmayı kolaylaştırır. OOP'nin sayısız faydalarını listelemek için yeterli alan yok.
Despertar

2
@ Phoshi: Evet, anlıyorum. İşlevsel bir yaklaşımın işe yaramayacağını asla iddia etmedim. Ancak, bu açıkça bir konu değil. Bir video kompresörü veya bir video aktarıcı veya bir nesne için hala geçerli bir aday olan şey.
GELMEKTEDİR

13

Belirli bir yöntemi özel bir sınıfa çıkarmak için nedenler olabilir. Bu nedenlerden biri, Bağımlılık Enjeksiyonuna izin vermektir.

VideoExporterSonunda bir videoyu sıkıştırabilecek bir sınıfınız olduğunu hayal edin . Temiz bir yol bir arayüze sahip olmak olacaktır:

interface IVideoCompressor
{
    Stream compress(Video video);
}

bunun gibi uygulanacak olan:

class MpegVideoCompressor : IVideoCompressor
{
    // ...
}

class FlashVideoCompressor : IVideoCompressor
{
    // ...
}

ve böyle kullanılır:

class VideoExporter
{
    // ...
    void export(Destination destination, IVideoCompressor compressor)
    {
        // ...
        destination = compressor(this.source);
        // ...
    }
    // ...
}

Bir kötü alternatif bir var olacaktır VideoExportersıkıştırma dahil tüm iş, kamu yöntemlerin bol ve bağlantıda bulunduğu. Hızla bir bakım kabusu olur ve diğer video formatlarını desteklemeyi zorlaştırır.


2
Cevabınız kamu ve özel yöntemler arasında ayırım yapmaz. Bir sınıfın tüm kodunu tek bir yöntemle koymanın bir öneri olarak anlaşılabilir, ancak demek istediğin bu değil mi?
Doktor Brown,

2
@DocBrown: Özel yöntemler bu soru ile ilgili değil. Dahili yardımcı sınıfına veya her neyse üzerine yerleştirilebilirler.
Jan Hudec

2
@JanHudec: şu anda metin: "Kötü bir alternatif, bol miktarda yönteme sahip bir VideoExporter'a sahip olmak olacaktır" - ancak "Kötü bir alternatif, bol miktarda kamusal yöntemlere sahip bir VideoExporter'a sahip olmak olacaktır " şeklindedir.
Doktor Brown

@DocBrown: Burada katılıyorum.
Jan Hudec

2
@DocBrown: Yorumlarınız için teşekkür ederiz. Cevabımı düzenledim. Başlangıçta, sorunun (ve dolayısıyla cevabımın) yalnızca kamuya açık yöntemlerle ilgili olduğu varsayıldığını düşündüm. Göründüğü kadar açık değil.
Arseni Mourzenko

6

Bu, işlevleri diğer işlevlere argüman olarak iletmek istediğiniz bir işarettir. Dilinizi (Java?) Desteklemiyor sanırım; eğer öyleyse, seçtiğiniz dilde bir eksiklik olduğu için tasarımınızda çok fazla bir başarısızlık olmaz. Bu, her şeyin bir sınıf olması gerektiğinde ısrar eden dillerle ilgili en büyük sorunlardan biri.

Bu hata fonksiyonlarını gerçekten geçemiyorsanız, sadece serbest / statik bir fonksiyon istersiniz.


1
Java 8'den bu yana, lambdalarınız vardır, bu yüzden hemen hemen işlevleri geçebilirsiniz.
Silviu Burcea,

Adil nokta, ancak bu resmi olarak bir süre daha resmi olarak yayınlanmayacak ve bazı işyerleri daha yeni sürümlere geçmekte yavaşlar.
Doval

Çıkış tarihi Mart ayındadır. Ayrıca, EAP JDK 8 için çok popülerdi :)
Silviu Burcea

Java 8 lambdaları, yalnızca bir genel yöntemle nesneleri tanımlamak için kısa bir yol olmasına rağmen, küçük bir parça kod kodunu kaydetmek dışında, burada hiçbir fark yaratmaz.
Jules

5

Partiye geç kaldığımı biliyorum ama her biri bunu belirtmek için kaçırmış görünüyor:

Bu iyi bilinen bir tasarım deseni: Strateji Deseni .

Strateji modeli, bir alt problemi çözmek için birkaç olası strateji olduğunda kullanılır. Genelde, tüm uygulamalar için bir sözleşme uygulayan bir arayüz tanımlar ve sonra sizin için somut bir strateji sağlamak için bir tür Bağımlılık Enjeksiyonu kullanırsınız.

Örneğin, bu durumda interface VideoCompressor, örneğin class H264Compressor implements VideoCompressorve için birkaç alternatif uygulamaya sahip olabilirsiniz class XVidCompressor implements VideoCompressor. OP ile ilgili bir arayüz olduğu açık değildir, olmasa da, orijinal yazarın gerektiğinde strateji modelini uygulamak için kapıyı açık bırakması olabilir. İçinde kendi başına da iyi tasarım.

OP'nin sürekli bir yöntemi çağırmak için sınıfları başlatırken kendini bulması problemi, bağımlılık enjeksiyonunu ve strateji modelini doğru kullanmamakla ilgili bir problemdir. İhtiyacınız olan yerde onu örneklemek yerine, içeren sınıfın strateji nesnesine sahip bir üyesi olmalıdır. Ve bu üyeye, örneğin yapıcıya enjekte edilmelidir.

Birçok durumda, strateji kalıbı sadece (tek bir doStuff(...)yöntemle ) arayüz sınıflarına (gösterdiğiniz gibi) neden olur .


1
public interface IVideoProcessor
{
   void Split();

   void Compress();

   void Index();
}

Sahip olduğunuz modüler ve bu iyi, ancak bu sorumlulukları IVideoProcessor'da gruplandırmak isteseydiniz, muhtemelen DDD açısından daha mantıklı olurdu.

Öte yandan, bölme, sıkıştırma ve dizine ekleme, herhangi bir şekilde ilişkili değilse, bunları ayrı bileşenler olarak tutmaya devam ediyorum.


Değişmesi gereken bu işlevlerin her birinin nedeni biraz farklı olduğundan, bunları bir araya getirmenin SRP'yi ihlal ettiğini iddia ediyorum.
Jules

0

Bu bir problem - veriden ziyade tasarımın işlevsel yönünden çalışıyorsunuz. Aslında sahip olduğunuz şey, OO-ifed olan 3 bağımsız fonksiyondur.

Örneğin, bir VideoCompressor sınıfınız var. Neden videoyu sıkıştırmak için tasarlanmış bir sınıfla çalışıyorsunuz - neden bu türdeki her nesnenin içerdiği (video) verilerini sıkıştırmak için üzerinde yöntemleri olan bir Video sınıfınız yok?

OO sistemlerini tasarlarken, uygulayabileceğiniz etkinlikleri temsil eden sınıfları değil, nesneleri temsil eden sınıfları oluşturmak en iyisidir. Eski günlerde sınıflara türler deniyordu - OO, yeni veri türlerini destekleyen bir dili genişletmenin bir yoluydu. OO'yu böyle düşünürseniz, sınıflarınızı tasarlamak için daha iyi bir yol elde edersiniz.

DÜZENLE:

Kendimi biraz daha iyi açıklamaya çalışmama izin verin, concat metoduna sahip bir string sınıf hayal edin. Sınıftan başlatılan her nesnenin string verisini içerdiği böyle bir şey uygulayabilirsiniz.

string mystring("Hello"); 
mystring.concat("World");

ancak OP bunun böyle çalışmasını istiyor:

string mystring();
string result = mystring.concat("Hello", "World");

Şimdi bir sınıfın ilgili işlevler koleksiyonunu tutmak için kullanılabileceği yerler var, ancak bu OO değil, kod tabanınızı daha iyi yönetmenize yardımcı olmak için bir dilin OO özelliklerini kullanmanın kullanışlı bir yolu "OO Tasarım". Bu tür durumlarda nesne tamamen yapaydır, basitçe böyle kullanılır çünkü dil bu tür bir sorunu yönetmek için daha iyi bir şey sunmaz. Örneğin. C # gibi dillerde, bu işlevselliği sağlamak için statik bir sınıf kullanırsınız - sınıf mekanizmasını yeniden kullanır, ancak artık üzerinde yalnızca yöntemleri çağırmak için bir nesneyi başlatmanız gerekmez. Sonunda string.IsNullOrEmpty(mystring), benim için karşılaştırıldığında, zayıf olduğunu düşündüğüm yöntemlerle bitiyorsunuz mystring.isNullOrEmpty().

Bu nedenle, herhangi biri "sınıflarımı nasıl tasarlarım" diye soruyorsa, sınıfın içerdiği işlevleri yerine içerecek verileri düşünmesini öneririm. "Bir sınıf bir yöntem yöntemidir" için giderseniz, o zaman "daha iyi" C "stil kodu yazmalısınız. (C kodunu geliştiriyorsanız bu mutlaka kötü bir şey değildir) ancak size en iyi OO tasarımlı programını vermeyecektir.


9
-1, kesinlikle katılmıyorum. Yeni başlayanlar OO tasarımı yalnızca a gibi veri nesneleriyle çalışır Videove genellikle sınıf başına> 10K LOC ile birlikte dağınık kodla biten işlevsellik ile bu sınıfları devirme eğilimindedir. Gelişmiş OO tasarımı, işlevselliği a gibi daha küçük birimlere böler VideoCompressor(ve bir Videosınıfın sadece bir veri sınıfı ya da cephesi olmasını sağlar VideoCompressor).
Doktor Brown,

5
@DocBrown: Bu noktada artık nesneye yönelik bir tasarım değil, çünkü nesneyiVideoCompressor temsil etmiyor . Bunda yanlış bir şey yok, sadece nesneye yönelik tasarımın sınırını gösterir.
Jan Hudec,

3
ah, ancak 1 işlevinin bir sınıfa dönüştüğü OO'lar gerçekten OO değildir ve yalnızca hiç kimsenin sahip olamayacağı binlerce sınıf dosyasında sona eren büyük ayrıştırmayı teşvik eder. Bir sınıfı bir "işlevsellik paketleyici" değil "veri sarmalayıcı" olarak düşünmenin en iyisi olduğunu düşünüyorum.
gbjbaanb

4
@DocBrown aslında endişelerimi doğru bir şekilde vurguladı; Gerçekten de bir Videosınıfım var, ancak sıkıştırma metodolojisi hiçbir şekilde önemsiz değildir, bu yüzden compressmetodu gerçekten başka birçok özel metoda bölerdim. Bu, sorumun bir parçası, okuduktan sonra fiil sınıfları oluşturma
yjwong

2
Bunun bir var Tamam olabileceğini düşünüyorum Videosınıfı, ancak olurdu oluşan a VideoCompressor, a VideoSplitter, iyi OO şeklinde, son derece yapışkan bireysel sınıflar olmalı ve diğer ilgili sınıfları.
Eric King,

-1

ISS (arayüz ayrımı prensibi) hiçbir istemci kullanmaz yöntemlere bağlıdır zorunda gerektiğini söylüyor. Avantajları çok ve açıktır. Yaklaşımınız ISS'ye tamamen saygı duyuyor ve bu iyi.

Ayrıca, ISS'ye saygı gösteren farklı bir yaklaşım, örneğin, her bir yöntem için bir arayüz (veya yüksek uyumu olan yöntemler kümesi) oluşturur ve daha sonra tüm bu arayüzleri uygulayan tek bir sınıfa sahiptir. Bunun daha iyi bir çözüm olup olmadığı senaryoya bağlıdır. Bunun yararı, bağımlılık enjeksiyonunu kullanırken, farklı ortak çalışanlarla (her arayüzde bir tane) bir müşteriniz olabilir ancak sonunda tüm ortak çalışanlar aynı nesne örneğine işaret edecektir.

Bu arada dedin ki

Kendimi her sınıfa yerleştirirken buluyorum

bu sınıflar hizmet gibi görünüyor (ve dolayısıyla vatansız). Onları singleton yapmayı düşündün mü?



3
Arayüz ayrımı ilkesi iyi bir kalıp iken geri kalanı için bu soru ara yüzlerle değil, uygulamalarla ilgilidir, bu yüzden konuyla ilgili olduğundan emin değilim.
Jan Hudec

3
Singleton'ın kalıp mı yoksa antipattern mi olduğunu tartışmak için girmeyeceğim. Sorununa olası bir çözüm (hatta bir adı bile var) önerdim, uygun olup olmadığına karar vermek ona bağlı.
diegomtassis,

ISS'nin alakalı olup olmadığına ilişkin olarak, sorunun konusu "sadece tek bir metotlu sınıflar mı?" Dır; bunun tam tersi, birçok yöntem içeren bir sınıftır, bu yaptığınız anda sorun olur. müşteri doğrudan buna bağlı ... tam olarak Robert Martin'in xerox örneği, ISS'yi formüle etmesine götürdü.
diegomtassis,

-1

Evet, bir sorun var. Fakat ciddi değil. Demek istediğim, kodunuzu bu şekilde yapılandırabilirsiniz ve kötü bir şey olmayacak, korunabilir. Ancak bu tür yapılanmada bazı zayıflıklar var. Örneğin, videonuzun gösterimi (sizin durumunda RawVideo sınıfında gruplandırılmışsa) değişirse, tüm işlem sınıflarınızı güncellemeniz gerekir. Veya çalışma zamanına göre değişkenlik gösteren birden fazla video gösterimi olabileceğini düşünün. O zaman temsili belirli "işlem" sınıfıyla eşleştirmeniz gerekir. Ayrıca tabi ki, yapmak istediğiniz her işlem için bir bağımlılığın etrafında sürüklenmek can sıkıcıdır. ve artık operasyona gerek olmadığına veya yeni bir işleme ihtiyacınız olduğuna karar verdiğiniz her seferde geçen bağımlılık listesini güncellemek için.

Ayrıca bu aslında bir SRP ihlalidir. Bazı insanlar SRP'ye sadece sorumlulukları bölmek için bir rehber gibi davranırlar (ve her bir operasyona ayrı bir sorumluluk uygulayarak bunu çok ileri götürürler), ancak SRP'nin de sorumlulukları gruplama konusunda bir rehber olduğunu unuturlar. SRP'ye göre, aynı nedenden ötürü değişen sorumluluklar birlikte gruplandırılmalıdır, böylece değişiklik olursa mümkün olduğu kadar az sınıf / modül yerelleştirilir. Büyük sınıflara gelince, bu algoritmalar ilişkili olduğu sürece aynı sınıfta birden fazla algoritmaya sahip olmak sorun değildir (yani, o sınıfın dışında bilinmemesi gereken bazı bilgileri paylaşın). Sorun, herhangi bir şekilde ilgili olmayan algoritmaları olan ve farklı sebeplerden dolayı değişen / değişen büyük sınıflardır.

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.