C # Farklı giriş parametrelerine sahip çalışanlar için Tasarım Deseni


14

Hangi tasarım modelinin bu sorunu çözmeme yardımcı olabileceğinden emin değilim.

Hangi İşçi sınıfının kullanılması gerektiğini belirleyen - Koordinatör ”adlı bir sınıfım var - tüm farklı İşçi türleri hakkında bilgi sahibi olmak zorunda kalmadan - sadece bir WorkerFactory'yi çağırıyor ve ortak IWorker arabirimi üzerinde hareket ediyor.

Daha sonra çalışmak için uygun İşçiyi ayarlar ve 'DoWork' yönteminin sonucunu döndürür.

Bu iyi oldu ... şimdiye kadar; yeni bir İşçi sınıfı için yeni bir gereksinimimiz var, "İşçiB", işini yapabilmesi için ek miktarda bilgi yani ek bir giriş parametresi gerektirir.

Ekstra giriş parametresi ile aşırı yüklenmiş bir DoWork yöntemine ihtiyacımız var ... ama sonra mevcut tüm İşçiler bu yöntemi uygulamak zorunda kalacaktı - ki bu İşçiler gerçekten bu yönteme ihtiyaç duymadığı için yanlış görünüyor.

Koordinatörü hangi İşçinin kullanıldığından habersiz tutmak ve yine de her İşçiye işini yapmak için ihtiyaç duyduğu bilgileri almasına izin vermek, ancak herhangi bir İşçinin ihtiyaç duymadığı şeyleri yapmamasına izin vermek için bunu nasıl yeniden düzenleyebilirim?

Halihazırda çok sayıda Çalışan var.

Yeni WorkerB sınıfının gerekliliklerini karşılamak için mevcut somut İşçilerinden hiçbirini değiştirmek istemiyorum.

Belki bir Dekoratör deseninin burada iyi olacağını düşündüm ama herhangi bir Dekoratörün daha önce aynı yöntemle ama farklı parametrelerle bir nesneyi süslediğini görmedim ...

Koddaki durum:

public class Coordinator
{
    public string GetWorkerResult(string workerName, int a, List<int> b, string c)
    {
        var workerFactor = new WorkerFactory();
        var worker = workerFactor.GetWorker(workerName);

        if(worker!=null)
            return worker.DoWork(a, b);
        else
            return string.Empty;
    }
}

public class WorkerFactory
{
    public IWorker GetWorker(string workerName)
    {
        switch (workerName)
        {
            case "WorkerA":
                return new ConcreteWorkerA();
            case "WorkerB":
                return new ConcreteWorkerB();
            default:
                return null;
        }
    }
}

public interface IWorker
{
    string DoWork(int a, List<int> b);
}

public class ConcreteWorkerA : IWorker
{
    public string DoWork(int a, List<int> b)
    {
        // does the required work
        return "some A worker result";
    }
}

public class ConcreteWorkerB : IWorker
{
    public string DoWork(int a, List<int> b, string c)
    {
        // does some different work based on the value of 'c'
        return "some B worker result";
    }

    public string DoWork(int a, List<int> b)
    {
        // this method isn't really relevant to WorkerB as it is missing variable 'c'
        return "some B worker result";
    }    
}

IWorkerArabirim eski sürümü listeledi mi , yoksa ek parametre içeren yeni bir sürüm mü?
JamesFaix

Kod tabanınızda şu anda 2 parametreli IWorker kullanan yerlerin 3. parametreyi takması gerekecek mi, yoksa yalnızca yeni arama siteleri 3. parametreyi mi kullanacak?
JamesFaix

2
Bir model için alışveriş yapmak yerine, bir modelin geçerli olup olmadığına bakılmaksızın genel tasarıma odaklanmayı deneyin . Önerilen okuma: “Modeller için Alışveriş” tipi sorular ne kadar kötü?

1
Kodunuza göre, IWorker örneği oluşturulmadan önce gereken tüm parametreleri zaten biliyorsunuz. Bu nedenle, bu argümanları DoWork yöntemine değil yapıcıya iletmelisiniz. IOW, fabrika sınıfınızı kullanın. Örneği oluşturma ayrıntılarını gizlemek, fabrika sınıfının varlığının hemen hemen ana nedenidir. Bu yaklaşımı benimsediyseniz çözüm önemsizdir. Ayrıca, bunu gerçekleştirmeye çalıştığınız şekilde başarmaya çalıştığınız şey kötü OO'dur. Liskov İkame İlkesini ihlal ediyor.
Dunk

1
Sanırım başka bir seviyeye geri dönmelisin. Coordinatorzaten bu ekstra parametreyi GetWorkerResultişlevine uyacak şekilde değiştirmeliydi - yani SOLID'in Açık-Kapalı-Prensibi ihlal edildi. Sonuç olarak, tüm kod çağrısı Coordinator.GetWorkerResultda değiştirilmeliydi. Bu işlevi çağırdığınız yere bakın: hangi IWorker'ı talep edeceğinize nasıl karar veriyorsunuz? Bu daha iyi bir çözüme yol açabilir.
Bernhard Hiller

Yanıtlar:


9

Temel arayüzü ve değişken sayıda alan veya özellik içeren tek bir parametreye sığacak şekilde bağımsız değişkenleri genelleştirmeniz gerekir. Bunun gibi:

public interface IArgs
{
    //Can be empty
}

public interface IWorker
{
    string DoWork(IArgs args);
}

public class ConcreteArgsA : IArgs
{
    public int a;
    public List<int> b;
}

public class ConcreteArgsB : IArgs
{
    public int a;
    public List<int> b;
    public string c;
}

public class ConcreteWorkerA : IWorker
{
    public string DoWork(IArgs args)
    {
        var ConcreteArgs = args as ConcreteArgsA;
        if (args == null) throw new ArgumentException();
        return "some A worker result";
    }
}

public class ConcreteWorkerB : IWorker
{
    public string DoWork(IArgs args)
    {
        var ConcreteArgs = args as ConcreteArgsB;
        if (args == null) throw new ArgumentException();
        return "some B worker result";
    }
} 

Boş kontrollere dikkat edin ... sisteminiz esnek ve geç bağlı olduğundan, aynı zamanda güvenli değildir, bu nedenle iletilen argümanların geçerli olduğundan emin olmak için yayınınızı kontrol etmeniz gerekir.

Gerçekten olası her argüman kombinasyonu için somut nesneler oluşturmak istemiyorsanız, bunun yerine bir demet kullanabilirsiniz (benim ilk tercihim olmaz.)

public string GetWorkerResult(string workerName, object args)
{
    var workerFactor = new WorkerFactory();
    var worker = workerFactor.GetWorker(workerName);

    if(worker!=null)
        return worker.DoWork(args);
    else
        return string.Empty;
}

//Sample call
var args = new Tuple<int, List<int>, string>(1234, 
                                             new List<int>(){1,2}, 
                                             "A string");    
GetWorkerResult("MyWorkerName", args);

1
Bu, Windows Forms uygulamalarının olaylarla nasıl başa çıktığına benzer. 1 "args" parametresi ve bir "olayın kaynağı" parametresi. Tüm "bağımsız değişkenler" EventArgs alt sınıfındadır: msdn.microsoft.com/en-us/library/… -> Bu kalıbın çok iyi çalıştığını söyleyebilirim. Sadece "Tuple" önerisini sevmiyorum.
Machado

if (args == null) throw new ArgumentException();Artık bir IWorker'ın her tüketicisi beton tipini bilmelidir - ve arayüz işe yaramaz: ondan kurtulabilir ve bunun yerine beton tiplerini kullanabilirsiniz. Ve bu kötü bir fikir, değil mi?
Bernhard Hiller

IWorker arabirimi, takılabilir mimari nedeniyle gereklidir ( WorkerFactory.GetWorkeryalnızca bir dönüş türüne sahip olabilir). Bu örneğin kapsamı dışındayken, arayanın bir workerName; muhtemelen uygun argümanlar da bulabilir.
John Wu

2

@ Dunk'ın yorumuna dayanarak çözümü yeniden tasarladım:

... IWorker örneği oluşturulmadan önce gereken tüm parametreleri zaten biliyorsunuz. Bu nedenle, bu argümanları DoWork yöntemine değil yapıcıya iletmelisiniz. IOW, fabrika sınıfınızı kullanın. Örneği oluşturma ayrıntılarını gizlemek, fabrika sınıfının varlığının hemen hemen ana nedenidir.

Bu yüzden bir IWorker oluşturmak için gerekli tüm argümanları IWorerFactory.GetWorker yöntemine kaydırdım ve sonra her işçi zaten ihtiyaç duyduğu şeye sahip ve Koordinatör sadece worker.DoWork ();

    public interface IWorkerFactory
    {
        IWorker GetWorker(string workerName, int a, List<int> b, string c);
    }

    public class WorkerFactory : IWorkerFactory
    {
        public IWorker GetWorker(string workerName, int a, List<int> b, string c)
        {
            switch (workerName)
            {
                case "WorkerA":
                    return new ConcreteWorkerA(a, b);
                case "WorkerB":
                    return new ConcreteWorkerB(a, b, c);
                default:
                    return null;
            }
        }
    }

    public class Coordinator
    {
        private readonly IWorkerFactory _workerFactory;

        public Coordinator(IWorkerFactory workerFactory)
        {
            _workerFactory = workerFactory;
        }

        // Adding 'c' breaks Open/Closed principal for the Coordinator and WorkerFactory; but this has to happen somewhere...
        public string GetWorkerResult(string workerName, int a, List<int> b, string c)
        {
            var worker = _workerFactory.GetWorker(workerName, a, b, c);

            if (worker != null)
                return worker.DoWork();
            else
                return string.Empty;
        }
    }

    public interface IWorker
    {
        string DoWork();
    }

    public class ConcreteWorkerA : IWorker
    {
        private readonly int _a;
        private readonly List<int> _b;

        public ConcreteWorkerA(int a, List<int> b)
        {
            _a = a;
            _b = b;
        }

        public string DoWork()
        {
            // does the required work based on 'a' and 'b'
            return "some A worker result";
        }
    }

    public class ConcreteWorkerB : IWorker
    {
        private readonly int _a;
        private readonly List<int> _b;
        private readonly string _c;

        public ConcreteWorkerB(int a, List<int> b, string c)
        {
            _a = a;
            _b = b;
            _c = c;
        }

        public string DoWork()
        {
            // does some different work based on the value of 'a', 'b' and 'c'
            return "some B worker result";
        }
    }

1
her üç durumda da kullanılmıyor olsa bile 3 parametre alan bir fabrika yönteminiz vardır. daha fazla parametreye ihtiyaç duyan bir C nesneniz varsa ne yapacaksınız? bunları yöntem imzasına ekler misiniz? bu çözüm uzatılamaz ve kötü tavsiye edilmez IMO
Amorphis

3
Daha fazla argümana ihtiyaç duyan yeni bir ConcreteWorkerC'ye ihtiyacım olsaydı, evet, GetWorker yöntemine eklenirler. Evet, Fabrika Açık / Kapalı prensibine uymuyor - ama bir yerde böyle bir şey olmalı ve bence Fabrika en iyi seçimdi. Benim önerim: bunun tavsiye edilmediğini söylemek yerine, aslında alternatif bir çözüm göndererek topluluğa yardımcı olacaksınız.
JTech

1

Birkaç şeyden birini öneririm.

Kapsüllemeyi korumak istiyorsanız, çağrı sitelerinin işçilerin veya işçi fabrikasının iç işleri hakkında hiçbir şey bilmek zorunda kalmaması durumunda, ekstra parametreye sahip olmak için arayüzü değiştirmeniz gerekir. Parametre varsayılan bir değere sahip olabilir, böylece bazı çağrı siteleri sadece 2 parametre kullanabilir. Bu, tüketen kitaplıkların yeniden derlenmesini gerektirecektir.

Enkapsülasyonu bozduğu ve genellikle kötü OOP olduğu için karşı tavsiye ederim diğer seçenek. Bu ayrıca en azından için tüm çağrı sitelerini değiştirmenizi gerektirir ConcreteWorkerB. IWorkerArabirimi uygulayan bir sınıf oluşturabilir , ancak DoWorkekstra parametreli bir yöntemi de kullanabilirsiniz . Daha sonra çağrı sitelerinizle IWorkerbirlikte yayınlamayı deneyin var workerB = myIWorker as ConcreteWorkerB;ve daha sonra DoWorkbeton tipinde üç parametreyi kullanın . Yine, bu kötü bir fikir, ama yapabileceğiniz bir şey.


0

@Jtech, bu paramsargümanın kullanımını düşündünüz mü? Bu, değişken miktarda parametrenin geçirilmesine izin verir.

https://msdn.microsoft.com/en-us/library/w5zay9db(v=vs.71).aspx


DoWork yönteminin her bağımsız değişken için aynı şeyi yapması ve her bağımsız değişken aynı türden olması durumunda params anahtar sözcüğü anlamlı olabilir. Aksi takdirde, DoWork yönteminin params dizisindeki her bağımsız değişkenin doğru türde olup olmadığını kontrol etmesi gerekir - ancak diyelim ki orada iki dize var ve her biri farklı bir amaç için kullanıldı, DoWork bunun doğru olduğundan emin olabilir mi? bir ... dizideki pozisyona göre varsaymak gerekir. Benim sevme için hepsi çok gevşek. @ JohnWu'nun çözümünün daha sıkı olduğunu hissediyorum.
JTech
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.