Bir yapıcı yerine bir fabrika yöntemi kullanmalıydım. Bunu değiştirebilir ve yine de geriye dönük uyumlu olabilir miyim?


15

Sorun

Diyelim ki bir dosyadan veri okumak için DataSourcebir ReadDatayöntem sağlayan (ve belki de diğerlerini basitleştirelim) adlı bir sınıfım var .mdb:

var source = new DataSource("myFile.mdb");
var data = source.ReadData();

Birkaç yıl sonra, veri kaynağı olarak dosyalara .xmlek olarak .mdbdosyaları destekleyebileceğime karar veriyorum . "Veri okuma" uygulaması .xmlve .mdbdosyalar için oldukça farklıdır ; bu nedenle, sistemi sıfırdan tasarlayacak olsaydım, şöyle tanımlardım:

abstract class DataSource {
    abstract Data ReadData();
    static DataSource OpenDataSource(string fileName) {
        // return MdbDataSource or XmlDataSource, as appropriate
    }
}

class MdbDataSource : DataSource {
    override Data ReadData() { /* implementation 1 */ }
}

class XmlDataSource : DataSource {
    override Data ReadData() { /* implementation 2 */ }
}

Harika, Fabrika yöntemi modelinin mükemmel bir uygulaması. Ne yazık ki, DataSourcebir kütüphanede yer alıyor ve kodun yeniden düzenlenmesi, mevcut tüm çağrıları kıracak

var source = new DataSource("myFile.mdb");

kütüphaneyi kullanan çeşitli istemcilerde. Vay be, neden ilk etapta fabrika metodunu kullanmadım?


Çözümler

Bunlar ortaya çıkarabileceğim çözümler:

  1. DataSource yapıcısının bir alt tür ( MdbDataSourceveya XmlDataSource) döndürmesini sağlayın . Bu bütün sorunlarımı çözer. Ne yazık ki, C # bunu desteklemiyor.

  2. Farklı adlar kullanın:

    abstract class DataSourceBase { ... }    // corresponds to DataSource in the example above
    
    class DataSource : DataSourceBase {      // corresponds to MdbDataSource in the example above
        [Obsolete("New code should use DataSourceBase.OpenDataSource instead")]
        DataSource(string fileName) { ... }
        ...
    }
    
    class XmlDataSource : DataSourceBase { ... }

    Ben kod geriye dönük uyumlu (yani new DataSource("myFile.mdb")hala çalışmaya çağırır ) tutar çünkü ben bunu kullanarak sona erdi . Dezavantajı: İsimler olması gerektiği kadar açıklayıcı değildir.

  3. DataSourceGerçek uygulama için bir "sarıcı" yapın :

    class DataSource {
        private DataSourceImpl impl;
    
        DataSource(string fileName) {
            impl = ... ? new MdbDataSourceImpl(fileName) : new XmlDataSourceImpl(fileName);
        }
    
        Data ReadData() {
            return impl.ReadData();
        }
    
        abstract private class DataSourceImpl { ... }
        private class MdbDataSourceImpl : DataSourceImpl { ... }
        private class XmlDataSourceImpl : DataSourceImpl { ... }
    }

    Dezavantaj: Her veri kaynağı yöntemi (örneğin ReadData), kaynak plakası koduyla yönlendirilmelidir. Ortak koddan hoşlanmıyorum. Gereksizdir ve kodu keser.

Herhangi var mı zarif kaçırdım o çözüm?


5
# 3 ile ilgili sorununuzu biraz daha ayrıntılı olarak açıklayabilir misiniz? Bu bana zarif geliyor. (Ya da geriye dönük uyumluluğu korurken elde ettiğiniz kadar zarif.)
pdr

API'yı yayınlayan bir arabirim tanımlarım ve daha sonra fabrikanın arabirimi uygulayan sınıfların karşılık gelen örneklerini oluşturmasını sağlayarak bir fabrikanın eski kodunuzun etrafında bir sarmalayıcı ve yeni kodlar oluşturmasını sağlayarak mevcut yöntemi yeniden kullanırdım.
Thomas

@pdr: 1. Yöntem imzalarında her değişiklik bir yerde daha yapılmalıdır. 2. İstemci yalnızca bir Xml veri kaynağında bulunan belirli işlevlere erişmek isterse, Impl sınıflarını iç ve özel yapabilirim. Veya onları herkese açık hale getirebilirim, yani müşteriler aynı şeyi yapmanın iki farklı yoluna sahip.
Heinzi

2
@Heinzi: Seçenek 3'ü tercih ederim. Bu standart "Cephe" kalıbıdır. Her veri kaynağı yöntemini uygulamaya veya yalnızca bazılarına gerçekten devretmeniz gerekip gerekmediğini kontrol etmelisiniz . Belki de hala "DataSource" kalır bazı genel kod var?
Doc Brown

newSınıf nesnesinin bir yöntemi olmayan bir utanç (böylece sınıfın kendisini - metasınıflar olarak bilinen bir teknik - alt sınıfı sınıflandırabilir ve newgerçekte ne yaptığını kontrol edebilirsiniz ) ama C # (veya Java veya C ++) böyle çalışmaz.
Donal Fellows

Yanıtlar:


12

Eski, çok genel bir adın aşamalı olarak kaldırılmasına izin veren ikinci seçeneğinize bir varyant için gideceğim DataSource:

abstract class AbstractDataSource { ... } // corresponds to the abstract DataSource in the ideal solution

class XmlDataSource : AbstractDataSource { ... }
class MdbDataSource : AbstractDataSource { ... } // contains all the code of the existing DataSource class

[Obsolete("New code should use AbstractDataSource instead")]
class DataSource : MdbDataSource { // an 'empty shell' to keep old code working.
    DataSource(string fileName) { ... }
}

Buradaki tek dezavantaj, yeni temel sınıfın en belirgin ada sahip olamamasıdır, çünkü bu ad orijinal sınıf için zaten talep edilmiştir ve geriye dönük uyumluluk için böyle kalması gerekir. Diğer tüm sınıfların tanımlayıcı adları vardır.


1
+1, soruyu okuduğumda aklıma tam olarak gelen buydu. Ben OP 3 seçenek daha hoş olsa da.
Doc Brown

Tüm yeni kodu yeni bir ad alanına koyarsanız, temel sınıf en belirgin ada sahip olabilir. Ama bunun iyi bir fikir olduğundan emin değilim.
svick

Temel sınıf, "Base" sonekine sahip olmalıdır. sınıf DataSourceBase
Stephen

6

En iyi çözüm, 3. seçeneğinize yakın bir şey olacaktır. DataSourceÇoğunluğu şimdi olduğu gibi tutun ve sadece okuyucu kısmını kendi sınıfına çıkarın.

class DataSource {
    private Reader reader;

    DataSource(string fileName) {
        reader = ... ? new MdbReader(fileName) : new XmlReader(fileName);
    }

    Data ReadData() {
        return reader.next();
    }

    abstract private class Reader { ... }
    private class MdbReader : Reader { ... }
    private class XmlReader : Reader { ... }
}

Bu şekilde, yinelenen koddan kaçınırsınız ve diğer uzantılara açık olursunuz.


+1, güzel seçenek. Yine de açıkça tercih ederek XmlDataSource'a özgü işlevselliğe erişebilmem için seçenek 2'yi tercih ediyorum XmlDataSource.
Heinzi
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.