I'm taking over some code for a project and I've seen a bunch of copied code in multiple classes. The owner is very weary about refactoring this code but I came up with an idea that sounded good to him.
开发者_JS百科Given:
Multiple "Client" classes that do NOT have the exact same interface but fairly close:
class Client1
{
public static string FunctionA(a);
public static string FunctionB(a, b, c);
public static string FunctionC(a, b);
}
class Client2
{
public static string FunctionA(a);
public static string FunctionB(a, b, c);
public static string FunctionC(a, b);
}
class Client3
{
public static string FunctionA(a);
public static string FunctionC(a, b);
public static string FunctionD();
}
... etc
Lets say that FunctionA
is function that is EXACTLY the same in each class. The owner somehow thinks that this function needs to be in each class because "it could be different for another client in the future" (fyi: FunctionA converts standard time to military time... so I highly doubt this).
Each of his clients has a special code (i.e. "abc" or "xyz" in the web.config
file so when the client's code is accessed, it gets the correct behavior using code similar to this:
public static string FunctionA(string a)
{
switch(getClientCode())
{
case "abc":
return Client1.FunctionA(a);
case "xyz":
return Client2.FunctionA(a);
case "def":
return Client3.FunctionA(a);
default:
throw new Exception("code not supported");
}
}
Let it be known that I do not in any way think this is ideal. I've actually fought with my client (who owns this code) in some fairly heated discussions about the decisions he has made with this project so don't shoot the messenger.
My client believes this way of doing things is useful when, say, I want to implement a new Client, I can just run the application and go through some steps until I find and "fix" these thrown exceptions. This is why the owner likes how the code is set up this way. However, about half the functions in each Client class are the same for each client OR they are the same for about 80% of the clients.
I asked him why he just didn't have an abstract class and the reason being was that not every derived class needs to or should implement any of the base classes functions. Also, all of the public functions are static (and there are no member variables) so it doesn't make sense to instantiate objects.
My Pattern:
Using the client classes from above, I would like to implement something like this:
class Client1
{
// FunctionA is the same for each class
//public static string FunctionA(a);
public static string FunctionB(string a, string b, string c);
// Client1 and Client3 share the same code.
//public static string FunctionC(a, b);
}
class Client2
{
// FunctionA is the same for each class
//public static string FunctionA(a);
public static string FunctionB(string a, string b, string c);
public static string FunctionC(string a, string b);
}
class Client3
{
// FunctionA is the same for each class
//public static string FunctionA(a);
// Client1 and Client3 share the same code.
//public static string FunctionC(a, b);
public static string FunctionD();
}
... etc.
class DefaultClient
{
public static string FunctionA(string a);
public static string FunctionB(string a, string b, string c);
public static string FunctionC(string a, string b);
}
class ProxyUtility
{
private static string getClientCode();
public static string FunctionA(string a)
{
switch (getClientCode())
{
case "abc":
case "def":
case "xyz":
return DefaultClient.FunctionA(a);
default:
throw new Exception("code not supported");
}
}
public static string FunctionB(string a, string b, string c)
{
switch (getClientCode())
{
case "abc":
case "xyz":
return DefaultClient.FunctionB(a, b, c);
case "def":
return string.Empty; // or throw an exception since they don't support this
default:
throw new Exception("code not supported");
}
}
public static string FunctionC(string a, string b)
{
switch (getClientCode())
{
case "abc":
case "def":
return DefaultClient.FunctionC(a, b);
case "xyz":
return Client2.FunctionC(a, b);
default:
throw new Exception("code not supported");
}
}
public static string FunctionD()
{
switch (getClientCode())
{
case "abc":
case "xyz":
return string.Empty; // or throw an exception since they don't support this function.
case "def":
return Client3.FunctionD();
default:
throw new Exception("code not supported");
}
}
}
Here's a flow chart as well to get an understand of how this works:
By the look of your code , yes , there is a name for your pattern.
It is called: Procedural Programming
Both ways are a mess to maintain. Also, the static method thing is causing lots of problems since you can't override them.
You should instead use the factory pattern. First set up your interfaces and clients
// put all methods any client will ever need here
public interface IClient
{
string FunctionA(string a);
string FunctionB(string a, string b, string c);
string FunctionC(string a, string b);
string FunctionD();
}
// now give a nice default implementation
internal abstract class DefaultClient : IClient
{
//all clients have the same functionA, for now, don't even allow overriding
public string FunctionA(string a)
{
return "hello: " + a;
}
//function B and C differ from client to client make them abstract
public abstract string FunctionB(string a, string b, string c);
public abstract string FunctionC(string a, string b);
//functionD isn't usually needed, but sometimes has an implementation
public virtual string FunctionD()
{
// do nothing I guess
}
}
internal class Client1 : DefaultClient
{
// implement functionB and functionC here
}
internal class Client2 : DefaultClient
{
// implement functionB and functionC here
}
internal class Client3 : DefaultClient
{
// implement functionB, functionC and functionD here
}
Now all you need to do is create the correct client class at run time. You are about to experience object oriented bliss.
// here is the factory
public static class ClientFactory
{
public static IClient GetClient(string clientCode)
{
// only 1 place in the code with a switch statement :)
switch (clientCode)
{
case "abc": return new Client1();
case "def": return new Client2();
case "xyz": return new Client3();
default: throw new Exception("code not supported: " clientCode);
}
}
}
Assuming that you don't have typos and fix all "static" issues what you are trying to do is "Template Design pattern". More information avaialable here http://www.dofactory.com/Patterns/PatternTemplate.aspx
There's a great book called Head First Design Patterns. The first chapter discusses creating interfaces for "behaviors." You could have a single client class that can use certain behaviors via a client factory.
Also look into some IOC (inversion of control) frameworks so that you can easily inject the behaviors into your client classes at runtime.
Admitting I just scanned through and did not read everything fully. ;-)
There are a couple of ways to tackle this, depending on the exact needs. If you can fit the same exact interface, you have the option of a factory pattern. Assume that is out, at least directly. But, you can set up a facade that makes a common "interface" and then use a factory (or provider, etc).
If roughly the same work is being done, but with slightly different methods, you can aim for a unit of work or a strategy pattern fairly easily.
In general, you want to get away from switch statements, if at all possible, as you are very likely to break the open/closed principle (open for extension, but closed for change).
Read up on the Decorator Pattern. Here's a sample chapter on it from the most excellent Head First Design Patterns.
http://oreilly.com/catalog/hfdesignpat/chapter/ch03.pdf
The decorator pattern is a way of dynamically extending a class with new responsibilities at runtime.
精彩评论