C # Pola Desain untuk pekerja dengan parameter input berbeda

14

Saya tidak yakin pola desain mana yang dapat membantu saya mengatasi masalah ini.

Saya memiliki kelas, 'Koordinator', yang menentukan kelas Pekerja mana yang harus digunakan - tanpa harus tahu tentang semua jenis Pekerja yang ada - hanya memanggil WorkerFactory dan bertindak berdasarkan antarmuka IWorker yang umum.

Kemudian menetapkan Pekerja yang sesuai untuk bekerja dan mengembalikan hasil metode 'DoWork'.

Ini baik-baik saja ... sampai sekarang; kami memiliki persyaratan baru untuk kelas Pekerja baru, "WorkerB" yang membutuhkan sejumlah informasi tambahan yaitu parameter input tambahan, agar dapat melakukan tugasnya.

Seperti kita membutuhkan metode DoWork yang kelebihan beban dengan parameter input ekstra ... tapi kemudian semua Pekerja yang ada harus menerapkan metode itu - yang tampaknya salah karena Pekerja itu benar-benar tidak memerlukan metode itu.

Bagaimana saya bisa menolak ini agar Koordinator tidak mengetahui Pekerja mana yang sedang digunakan dan masih memungkinkan setiap Pekerja untuk mendapatkan informasi yang diperlukan untuk melakukan tugasnya tetapi tidak ada Pekerja yang melakukan hal-hal yang tidak perlu?

Sudah banyak Pekerja yang ada.

Saya tidak ingin harus mengubah salah satu Pekerja beton yang ada untuk mengakomodasi persyaratan kelas WorkerB baru.

Saya pikir mungkin pola Penghias akan baik di sini, tetapi saya belum melihat Penghias menghiasi objek dengan metode yang sama tetapi parameter yang berbeda sebelum ...

Situasi dalam kode:

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";
    }    
}
JTech
sumber
Apakah IWorkerantarmuka terdaftar versi lama, atau apakah itu versi baru dengan parameter yang ditambahkan?
JamesFaix
Apakah tempat-tempat di basis kode Anda yang saat ini menggunakan IWorker dengan 2 parameter perlu memasukkan parameter ke-3, atau hanya situs panggilan baru yang akan menggunakan parameter ke-3?
JamesFaix
2
Alih-alih berbelanja untuk suatu pola, cobalah berfokus pada desain keseluruhan terlepas dari apakah suatu pola berlaku atau tidak. Bacaan yang disarankan: Seberapa buruk pertanyaan "Belanja untuk Pola"?
1
Menurut kode Anda, Anda sudah tahu semua parameter yang diperlukan sebelum instance IWorker dibuat. Dengan demikian, Anda harus meneruskan argumen tersebut ke konstruktor dan bukan metode DoWork. TKI, manfaatkan kelas pabrik Anda. Menyembunyikan detail pembuatan instance cukup banyak menjadi alasan utama keberadaan kelas pabrik. Jika Anda mengambil pendekatan itu maka solusinya sepele. Juga, apa yang Anda coba capai dengan cara yang Anda coba capai itu adalah OO yang buruk. Itu melanggar Prinsip Pergantian Liskov.
Dunk
1
Saya pikir Anda harus kembali ke level lain. Coordinatorsudah harus diubah untuk mengakomodasi parameter tambahan dalam GetWorkerResultfungsinya - itu berarti bahwa Prinsip Terbuka-Tertutup dari SOLID dilanggar. Sebagai akibatnya, semua panggilan kode Coordinator.GetWorkerResultharus diubah juga. Jadi lihat di tempat Anda memanggil fungsi itu: bagaimana Anda memutuskan IWorker untuk meminta? Itu mungkin mengarah pada solusi yang lebih baik.
Bernhard Hiller

Jawaban:

9

Anda perlu menggeneralisasi argumen agar sesuai dengan parameter tunggal dengan antarmuka dasar dan sejumlah variabel bidang atau properti. Semacam seperti ini:

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";
    }
} 

Perhatikan cek nol ... karena sistem Anda fleksibel dan terikat, ini juga bukan tipe aman, jadi Anda perlu memeriksa gips untuk memastikan argumen yang disahkan valid.

Jika Anda benar-benar tidak ingin membuat objek konkret untuk setiap kemungkinan kombinasi argumen, Anda bisa menggunakan tuple sebagai gantinya (tidak akan menjadi pilihan pertama saya.)

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);
John Wu
sumber
1
Ini mirip dengan bagaimana aplikasi Windows Forms menangani peristiwa. 1 parameter "args", dan satu parameter "sumber acara". Semua "args" adalah subkelas dari EventArgs: msdn.microsoft.com/en-us/library/… -> Saya akan mengatakan bahwa pola ini bekerja dengan sangat baik. Saya hanya tidak suka saran "Tuple".
Machado
if (args == null) throw new ArgumentException();Sekarang setiap konsumen IWorker harus tahu jenis konkretnya - dan antarmuka tidak berguna: Anda juga dapat membuangnya dan menggunakan jenis beton sebagai gantinya. Dan itu ide yang buruk, bukan?
Bernhard Hiller
Antarmuka IWorker diperlukan karena arsitektur pluggable ( WorkerFactory.GetWorkerhanya dapat memiliki satu jenis kembali). Sementara di luar cakupan contoh ini, kita tahu penelepon dapat membuat sebuah workerName; mungkin itu bisa muncul dengan argumen yang tepat juga.
John Wu
2

Saya telah merekayasa ulang solusi berdasarkan komentar @ Dunk:

... Anda sudah tahu semua parameter yang diperlukan sebelum instance IWorker dibuat. Dengan demikian, Anda harus meneruskan argumen tersebut ke konstruktor dan bukan metode DoWork. TKI, manfaatkan kelas pabrik Anda. Menyembunyikan detail pembuatan instance cukup banyak menjadi alasan utama keberadaan kelas pabrik.

Jadi saya telah menggeser semua argumen yang mungkin diperlukan untuk membuat IWorker menjadi metode IWorerFactory.GetWorker dan kemudian masing-masing pekerja sudah memiliki apa yang dibutuhkannya dan Koordinator dapat memanggil pekerja saja. 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";
        }
    }
JTech
sumber
1
Anda memiliki metode pabrik yang menerima 3 parameter meskipun tidak semua 3 digunakan di semua situasi. apa yang akan Anda lakukan jika Anda memiliki objek C yang membutuhkan lebih banyak parameter? Anda akan menambahkannya ke tanda tangan metode? solusi ini tidak dapat diperpanjang dan disarankan IMO
Amorphis
3
Jika saya membutuhkan ConcreteWorkerC baru yang membutuhkan lebih banyak argumen, maka ya, mereka akan ditambahkan ke metode GetWorker. Ya, Pabrik tidak sesuai dengan prinsipal Open / Closed - tetapi sesuatu di suatu tempat harus seperti ini dan Pabrik menurut saya adalah pilihan terbaik. Saran saya adalah: daripada hanya mengatakan ini tidak disarankan, Anda akan membantu komunitas dengan benar-benar memposting solusi alternatif.
JTech
1

Saya akan menyarankan satu dari beberapa hal.

Jika Anda ingin mempertahankan enkapsulasi, sehingga panggilan tidak perlu tahu apa-apa tentang cara kerja pekerja atau pabrik pekerja, maka Anda harus mengubah antarmuka untuk memiliki parameter tambahan. Parameter dapat memiliki nilai default, sehingga beberapa callsite masih bisa menggunakan 2 parameter. Ini akan mengharuskan perpustakaan yang mengkonsumsi dikompilasi ulang.

Pilihan lain yang saya sarankan, karena itu merusak enkapsulasi dan umumnya OOP buruk. Ini juga mengharuskan Anda setidaknya dapat memodifikasi semua panggilan untuk ConcreteWorkerB. Anda bisa membuat kelas yang mengimplementasikan IWorkerantarmuka, tetapi juga memiliki DoWorkmetode dengan parameter tambahan. Kemudian dalam panggilan Anda, coba untuk membuang IWorkerdengan var workerB = myIWorker as ConcreteWorkerB;dan kemudian gunakan tiga parameter DoWorkpada jenis beton. Sekali lagi, ini adalah ide yang buruk, tetapi itu adalah sesuatu yang bisa Anda lakukan.

JamesFaix
sumber
0

@Jtech, sudahkah Anda mempertimbangkan penggunaan paramsargumen? Ini memungkinkan sejumlah variabel parameter dilewatkan.

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

Jon Raynor
sumber
Kata kunci params mungkin masuk akal jika metode DoWork melakukan hal yang sama dengan setiap argumen dan jika setiap argumen memiliki tipe yang sama. Jika tidak, metode DoWork perlu memeriksa bahwa setiap argumen dalam array params adalah dari jenis yang benar - tetapi katakanlah kita memiliki dua string di sana dan masing-masing digunakan untuk tujuan yang berbeda, bagaimana bisa DoWork memastikan bahwa ia memiliki yang benar satu ... itu harus mengasumsikan berdasarkan posisi dalam array. Semua terlalu longgar untuk seleraku. Saya merasa solusi JohnWu lebih ketat.
JTech