views:

200

answers:

11

Having 2 different interfaces is a must.

How would you refactor this?

Should I refactor this code at all?

private void CreateInstanceForProviderA()
{
    a = FactorySingleton.Instance.CreateInstanceA("A");

    if (a == null)
    {
        ShowProviderNotInstanciatedMessage();
        return;
    }

    a.Owner = Handle.ToInt32();
    lbl_Text.Text = a.Version();
}

private void CreateInstanceForProviderB()
{
    b = FactorySingleton.Instance.CreateInstanceB("B");

    if (b == null)
    {
        ShowProviderNotInstanciatedMessage();
        return;
    }

    b.Owner = Handle.ToInt32();
    lbl_Text.Text = b.Version();
}

If there would be a common interface, I could write:

private void CreateInstanceForProvider(string provider)
{

    p = FactorySingleton.Instance.CreateInstanceB(provider);
    // p is shared over the whole class
    if (p == null)
    {
        ShowProviderNotInstanciatedMessage();
        return;
    }

    var tmpProvider = p as ICommonProvider;

    tmpProvider .Owner = Handle.ToInt32();
    lbl_Text.Text = tmpProvider .Version();
}
A: 

I would leave it as it is, no refactoring necessary...
YET!
If/when ProviderC shows up, then I would refactor. 8 )

Task
how would you do this? The FactorySingleton is not my code. I think the code redundancy is not because of my code it lies deeper and component I use. But I am not sure :s
Rookian
DRY - DON'T Repeat Yourself. You have duplicate code in there. It should be refactored. It is a silly statement to say "no refactoring necessary"
Dave White
What, 'how would I refactor this when ProviderC appears'? Well, that's a little difficult seeing as how all that I can see here is that you're creating an object instance and throwing it away, but I could take a crack at it.
Task
@Dave: No, what's silly is refactoring every time you duplicate a single line of code. You need information to refactor well, or you'll end up refactoring the same thing multiple times and wasting time. "Three" is a good minimum requirement: http://sourcemaking.com/refactoring/when-should-you-refactor
Task
If you want to boil it down to working code, refactoring is not necessary. It works, so why change it? That is not a philosophy that I espouse. I refactor often and aggressively. If I see duplicate code, I remove it. What if I don't add Provider C but one of my teammates does? What if he isn't following the 3 strikes rule, or even knows that somewhere in the class there is are 2 previous strikes. I think it is important to refactor often and aggressively, but that is my approach and what I encourage others to do.
Dave White
You change it because it will make adding ProviderC simpler and less error-prone. Refactor is the step before adding the new functionality of ProviderC. That's my approach. Neither approach invalidates the other. What I would say is far more important is that the entire development team be using the same software development methodologies, whatever they are. If everyone on the team operates in a completely different manner, then you've got much bigger problems than can be solved by a refactor.
Task
+1  A: 

Perhaps have a third method that is private, and replace your code with calls to this third method, as so:

private void CreateInstanceForProviderA() { return DoSomething(); }

private void CreateInstanceForProviderB() { return DoSomething(); }

Nicholas Hill
sry I can not follow you
Rookian
If I read this right, he has no control over FactorySingleton and there none over CreateInstanceForProviderA
James Curran
A: 

It all depends on those method calls (.CreateInstanceA and .CreateInstanceB).

If they're doing the same things in the same way, and the only difference is that string parameter, then yes: refactor the method to CreateInstanceForProvider(string providerCode) and let the user / calling code pass in the proper parameter.

If they do slightly different things, then you may still be able to refactor, and it becomes more of a headache. At that point, you have to determine whether the increased abstraction (and additional ease of adding new Providers) is worth refactoring and having to re-run (and possibly rewrite) whatever tests are necessary.

AllenG
+5  A: 

Well, the first thing to do is to yell at the author of FactorySingleton to fix has damn code so that ClassA and ClassB have a common interface for their common fields.

In the meantime, you pretty much stuck using reflection, which would be ugly and not worth it for just that little bit.

James Curran
I'd kill, not yell.
strager
@strager: then, how would he fix his code?
James Curran
@James Curran, I wouldn't want them to fix the code. I'd rather have someone more competent fix it.
strager
A: 

EDIT: This does no work because a and b are different types (not sure if that was before or after I gave my answer...)

I'm assuming a and b are fields, not properties.

Abstractly, put the common functionality in a single method called by both of the original methods:

private void CreateInstanceForProviderA()
{
    a = CreateInstanceForProvider("A");
}

private void CreateInstanceForProviderB()
{
    b = CreateInstanceForProvider("B");
}

private FactorySingleton CreateInstanceForProvider(string which) 
{
    var instance = FactorySingleton.Instance.CreateInstanceB(which);

    if (instance == null)
    {
        ShowProviderNotInstanciatedMessage();
        return;
    }

    instance.Owner = Handle.ToInt32();
    lbl_Text.Text = instance.Version(); 

    return instance;
}
apollodude217
This abstraction won't work. He stated that 2 distinct interfaces is a requirement.
Dave White
@Dave - Now I see. I thought this answer was a little too easy.
apollodude217
A: 

I'd pass an enum in to specify the create instance code to call. After that create a wrapper class containing methods to get/set using reflection as suggested in other answers use reflection. Not sure it's worth it, as you code is probably harder to read than before.

    public enum Provider
    {
        A,
        B
    }

    private void CreateInstanceForProvider(Provider provider)
    {
        ProviderWrapper provider = null;

        switch (provider)
        {
            case Provider.A:
                provider = new ProviderWrapper(FactorySingleton.Instance.CreateInstanceA("A"));
                break;
            case Provider.B:
                provider = new ProviderWrapper(FactorySingleton.Instance.CreateInstanceB("B"));
                break;
        }

        if (provider == null)
        {
            ShowProviderNotInstanciatedMessage();
            return;
        }

        provider.SetOwner(Handle.ToInt32());
        lbl_Text.Text = provider.GetVersion();
    }

    public class ProviderWrapper
    {
        private readonly object _original;

        public ProviderWrapper(object original)
        {
            _original = original;
        }

        public void SetOwner(int value)
        {
            _original.GetType().GetProperty("Owner").SetValue(_original, value, null);
        }

        public string GetVersion()
        {
            return (String)_original.GetType().GetProperty("Owner").GetValue(_original, null);
        }
    }
Castrohenge
`object` doesn't have an `Owner` property or a `Version` method though...
Jon Skeet
I had a feeling I forgot something.
Castrohenge
How do you change the provider to the required type? It has been stated that there is no commonality (class inheritance or interface wise) between the providers. And I'm assuming he can't change that or else he wouldn't have asked the question. :)
Dave White
ya ... and both variables have a different interface
Rookian
I miss javascript
Castrohenge
A: 

Remove the duplicate code. In this case, you should be able to remove the middle message block that does the null check and message for a failed instantiation.

private void CreateInstanceForProviderA()
{
    a = FactorySingleton.Instance.CreateInstanceA("A");

    if (IsNullObject(a))
    {
       return;
    }

    a.Owner = Handle.ToInt32();
    lbl_Text.Text = a.Version();
}

private void CreateInstanceForProviderB()
{
    b = FactorySingleton.Instance.CreateInstanceB("B");

    if (IsNullObject(b))
    {
       return;
    }

    b.Owner = Handle.ToInt32();
    lbl_Text.Text = b.Version();
}

private bool IsNullObject(object obj)
{
    if (obj == null)
    {
        ShowProviderNotInstanciatedMessage();
        return true;
    }
    return false;
}

If you do find a way to provide a common interface or shared virtual methods on these providers, we can refactor more aggressively.

Dave White
+5  A: 

What version of C# are you using?

In C# 4 (Visual Studio 2010) the new dynamic keyword could help share code in this case. I wouldn't use if it this is a performance-critical section of code though, but if this just runs a handful of times then go ahead.

Ben Voigt
I use C# 4.0, but I think you should not fix the symptom. You should fix the ROOT PROBLEM.
Rookian
+3  A: 

InstanceA and InstanceB ought to implement a common interface.

public interface IA : ICommon {...}
public interface IB : ICommon {...}

public interface ICommon
{
    int Owner {get;}
    string Version();
}

This way, you still have two different interfaces, but the common aspects of those interfaces are defined in a way that you can do some of the same things with both of them.

StriplingWarrior
+1  A: 
public interface ICommon
{
    int Owner { get; }
    string Version();
}

public interface IA : ICommon
public interface IB : ICommon

private void CreateInstanceForProvider(ICommon c) 
{ 
    if (c == null) 
    { 
        ShowProviderNotInstanciatedMessage(); 
        return; 
    } 

    c.Owner = Handle.ToInt32(); 
    lbl_Text.Text = c.Version(); 
} 
Jimmy Hoffa
Interfaces don't define method implementations (Version). Besides, I beat you to this answer. ;-)
StriplingWarrior
@StriplingWarrior: And I gave you the vote for it, but just threw out the method implementation anyway
Jimmy Hoffa
+1 for relying on the object itself as a parameter, by the way. Good for unit testing.
StriplingWarrior
+1  A: 

It's always painful to fight the type system. Without using dynamic, here goes my attempt.

Given that you have these two distinct interfaces for a and b:

interface IA {
  int Owner { set; }
  string Version();
}
interface IB {
  int Owner { set; }
  string Version();
}

You can create a wrapper type like this:

class WrapperAB : IA, IB {
  IA a; IB b;
  public WrapperAB(object o) {
    if (o is IA) a = (IA)o;
    else if (o is IB) b = (IB)o;
    else throw new Exception();
  }
  public int Owner {
    set { 
      if (a != null) a.Owner = value; 
      else b.Owner = value; 
    }
  }
  public string Version() {
    if (a != null) return a.Version();
    else return b.Version();
  }
}

And change your methods to this:

private void CreateInstanceForProviderA() {
  CreateInstanceForProvider<IA>("A", FactorySingleton.Instance.CreateInstanceA, out a);
}

private void CreateInstanceForProviderB() {
  CreateInstanceForProvider<IB>("B", FactorySingleton.Instance.CreateInstanceB, out b);
}

private void CreateInstanceForProvider<TI>(string name, Func<string, TI> factory, out TI instance) {
  instance = factory(name);

  if (instance == null) {
    ShowProviderNotInstanciatedMessage();
    return;
  }

  var w = new WrapperAB(instance);
  w.Owner = Handle.ToInt32();
  lbl_Text.Text = w.Version();
}
Jordão