Mẫu thiết kế C # cho công nhân với các thông số đầu vào khác nhau


14

Tôi không chắc mẫu thiết kế nào có thể giúp tôi giải quyết vấn đề này.

Tôi có một lớp, 'Điều phối viên', xác định lớp Công nhân nào sẽ được sử dụng - mà không cần phải biết về tất cả các loại Công nhân khác nhau - nó chỉ gọi một WorkerFactory và hoạt động trên giao diện IWorker chung.

Sau đó, nó thiết lập Công nhân thích hợp để làm việc và trả về kết quả của phương thức 'DoWork'.

Điều này đã ổn ... cho đến bây giờ; chúng tôi có một yêu cầu mới đối với một lớp Worker mới, "WorkerB", yêu cầu một lượng thông tin bổ sung, tức là một tham số đầu vào bổ sung, để nó thực hiện công việc của nó.

Giống như chúng ta cần một phương thức DoWork quá tải với tham số đầu vào bổ sung ... nhưng sau đó tất cả các Công nhân hiện tại sẽ phải thực hiện phương thức đó - điều này có vẻ sai vì những Công nhân đó thực sự không cần phương thức đó.

Làm cách nào tôi có thể cấu trúc lại điều này để giữ cho Điều phối viên không biết Công nhân nào đang được sử dụng và vẫn cho phép mỗi Công nhân nhận được thông tin cần thiết để thực hiện công việc của mình nhưng không có Công nhân nào làm những việc không cần thiết?

Có rất nhiều Công nhân hiện có.

Tôi không muốn phải thay đổi bất kỳ Công nhân cụ thể nào hiện có để đáp ứng các yêu cầu của lớp WorkerB mới.

Tôi nghĩ có lẽ một mẫu Trang trí sẽ tốt ở đây nhưng tôi chưa thấy Trang trí nào trang trí một đối tượng với cùng một phương thức nhưng các tham số khác nhau trước đó ...

Tình huống trong mã:

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

IWorkergiao diện được liệt kê phiên bản cũ, hoặc đó là một phiên bản mới với một tham số được thêm vào?
JamesFaix

Các vị trí trong cơ sở mã của bạn hiện đang sử dụng IWorker với 2 tham số sẽ cần phải cắm tham số thứ 3 hay chỉ các trang web cuộc gọi mới sẽ sử dụng tham số thứ 3?
JamesFaix

2
Thay vì đi mua một mẫu, hãy thử tập trung vào thiết kế tổng thể bất kể áp dụng mẫu đó hay không. Đề nghị đọc: Làm thế nào xấu là mua sắm trên mạng cho các câu hỏi kiểu mẫu?

1
Theo mã của bạn, bạn đã biết tất cả các tham số cần thiết trước khi phiên bản IWorker được tạo. Vì vậy, bạn nên chuyển các đối số đó cho hàm tạo chứ không phải phương thức DoWork. IOW, sử dụng lớp nhà máy của bạn. Ẩn các chi tiết xây dựng thể hiện là lý do chính cho sự tồn tại của lớp nhà máy. Nếu bạn đã thực hiện phương pháp đó thì giải pháp là tầm thường. Ngoài ra, những gì bạn đang cố gắng thực hiện theo cách bạn đang cố gắng để hoàn thành nó là OO xấu. Nó vi phạm Nguyên tắc thay thế Liskov.
Dunk

1
Tôi nghĩ bạn phải quay lại một cấp độ khác. Coordinatorđã phải thay đổi để phù hợp với tham số bổ sung đó trong GetWorkerResultchức năng của nó - điều đó có nghĩa là Nguyên tắc mở-đóng-mở của RẮN bị vi phạm. Kết quả là, tất cả các cuộc gọi mã Coordinator.GetWorkerResultcũng phải được thay đổi. Vì vậy, hãy nhìn vào nơi bạn gọi hàm đó: làm thế nào để bạn quyết định IWorker nào sẽ yêu cầu? Điều đó có thể dẫn đến một giải pháp tốt hơn.
Bernhard Hiller

Câu trả lời:


9

Bạn sẽ cần khái quát hóa các đối số để chúng khớp với một tham số duy nhất với giao diện cơ sở và số lượng trường hoặc thuộc tính thay đổi. Sắp xếp như thế này:

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

Lưu ý kiểm tra null ... vì hệ thống của bạn linh hoạt và bị ràng buộc muộn, nên nó cũng không phải là loại an toàn, do đó bạn sẽ cần kiểm tra diễn viên của mình để đảm bảo các đối số được thông qua là hợp lệ.

Nếu bạn thực sự không muốn tạo các đối tượng cụ thể cho mọi kết hợp có thể có của đối số, bạn có thể sử dụng bộ dữ liệu thay thế (sẽ không phải là lựa chọn đầu tiên của tôi.)

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
Điều này tương tự như cách các ứng dụng Windows Forms xử lý các sự kiện. 1 tham số "args" và một tham số "nguồn của sự kiện". Tất cả các "đối số" được phân lớp từ EventArss: msdn.microsoft.com/en-us/l Library / Lỗi -> Tôi muốn nói rằng mô hình này hoạt động rất tốt. Tôi chỉ không thích đề xuất "Tuple".
Machado

if (args == null) throw new ArgumentException();Bây giờ mọi người tiêu dùng của IWorker đều phải biết loại cụ thể của nó - và giao diện là vô dụng: bạn cũng có thể thoát khỏi nó và sử dụng các loại cụ thể thay thế. Và đó là một ý tưởng tồi, phải không?
Bernhard Hiller

Giao diện IWorker là bắt buộc do kiến ​​trúc có thể cắm ( WorkerFactory.GetWorkerchỉ có thể có một kiểu trả về). Trong khi ngoài phạm vi của ví dụ này, chúng tôi biết người gọi có thể đưa ra một workerName; có lẽ nó cũng có thể đưa ra những lý lẽ thích hợp.
John Wu

2

Tôi đã thiết kế lại giải pháp dựa trên nhận xét của @ Dunk:

... bạn đã biết tất cả các tham số cần thiết trước khi phiên bản IWorker được tạo. Vì vậy, bạn nên chuyển các đối số đó cho hàm tạo chứ không phải phương thức DoWork. IOW, sử dụng lớp nhà máy của bạn. Ẩn các chi tiết xây dựng thể hiện là lý do chính cho sự tồn tại của lớp nhà máy.

Vì vậy, tôi đã chuyển tất cả các đối số có thể cần để tạo IWorker sang phương thức IWorerFactory.GetWorker và sau đó mỗi công nhân đã có những gì nó cần và Điều phối viên chỉ có thể gọi 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
bạn có một phương thức xuất xưởng nhận 3 tham số mặc dù không phải cả 3 đều được sử dụng ở mọi tình huống. Bạn sẽ làm gì nếu bạn có một đối tượng C cần nhiều tham số hơn? bạn sẽ thêm chúng vào chữ ký phương thức chứ? giải pháp này không thể mở rộng và khuyên IMO
Amorp4

3
Nếu tôi cần một ConcreteWorkerC mới cần nhiều đối số hơn, thì có, chúng sẽ được thêm vào phương thức GetWorker. Có, Nhà máy không tuân thủ hiệu trưởng Mở / Đóng - nhưng một cái gì đó ở đâu đó phải như thế này và theo tôi, Nhà máy là lựa chọn tốt nhất. Đề nghị của tôi là: thay vì chỉ nói điều này không đúng, bạn sẽ giúp cộng đồng bằng cách thực sự đăng một giải pháp thay thế.
JTech

1

Tôi sẽ đề nghị một trong nhiều điều.

Nếu bạn muốn duy trì đóng gói, để các cuộc gọi không phải biết bất cứ điều gì về hoạt động bên trong của công nhân hoặc nhà máy công nhân, thì bạn sẽ cần thay đổi giao diện để có thêm tham số. Tham số có thể có giá trị mặc định, do đó một số cuộc gọi vẫn có thể chỉ sử dụng 2 tham số. Điều này sẽ yêu cầu bất kỳ thư viện tiêu thụ được biên dịch lại.

Tùy chọn khác tôi muốn giới thiệu, vì nó phá vỡ đóng gói và nói chung là OOP xấu. Điều này cũng yêu cầu bạn ít nhất có thể sửa đổi tất cả các cuộc gọi cho ConcreteWorkerB. Bạn có thể tạo một lớp thực hiện IWorkergiao diện, nhưng cũng có một DoWorkphương thức với một tham số phụ. Sau đó, trong các cuộc gọi của bạn cố gắng truyền IWorkervới var workerB = myIWorker as ConcreteWorkerB;và sau đó sử dụng ba tham số DoWorktrên loại cụ thể. Một lần nữa, đây là một ý tưởng tồi, nhưng nó là điều bạn có thể làm.


0

@Jtech, bạn đã xem xét việc sử dụng paramsđối số chưa? Điều này cho phép một lượng tham số khác nhau được thông qua.

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


Từ khóa params có thể có ý nghĩa nếu phương thức DoWork làm điều tương tự với mỗi đối số và nếu mỗi đối số có cùng loại. Mặt khác, phương thức DoWork sẽ cần kiểm tra xem mỗi đối số trong mảng params có đúng loại không - nhưng giả sử chúng ta có hai chuỗi trong đó và mỗi chuỗi được sử dụng cho một mục đích khác nhau, làm thế nào DoWork có thể đảm bảo rằng nó có đúng một ... nó sẽ phải giả định dựa trên vị trí trong mảng. Tất cả quá lỏng lẻo theo ý thích của tôi. Tôi cảm thấy giải pháp của @ JohnWu chặt chẽ hơn.
JTech
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.