2010-11-18 13 views
3

Kann ich eine Klassendefinition durch eine eigene Implementierung ersetzen (oder überschreiben)?Ersetzen einer Klassenimplementierung in C#

Erläuterung:

  • ich eine Klasse mit einer System.Random Instanz habe, und ich versuche, es zu Test ohne seinen ursprünglichen Code zu ändern ...
  • So Random Ich plane, ersetzen Klasse mit einer eigenen Implementierung, die es ermöglicht, generierte Sequenzen zu steuern. Siehe this question.

`

class foo 
{ 
    Random r=new Random(); 
    double bar() 
    { 
     double ans=r.NextDouble(); 
     .....// evaluate ans 
     return ans; 
    } 
} 

`

Was sind die Möglichkeiten, die ich Implementierung von Random-Klasse durch eine andere ersetzen kann (oder mit minimalen Änderungen) den ursprünglichen Code-Datei ohne Änderung ??

Bearbeiten:
Eine Lösung ist Random-Klasse zu erben und zu ändern ... aber das erfordert Ändern jeder Random-Instanz mit der geerbten ... Ich möchte den Code nicht ändern, weil ich es gerade testen !! Bearbeiten 2:
Kann ich in C# sagen: "Jede Random-Klasse in dieser Datei ist MyNamespace.Random nicht System.Random" ??

+0

Können Sie das klären? Möchten Sie nur einige ersetzen, wenn es Mitglieder sind? Möchten Sie es erweitern, um neue hinzuzufügen? – CodingGorilla

+1

Hört sich an wie du nach dem Erstellen von Mocks bist, könnte das sein? – Lucero

+0

@Lucero +1, dachte ich die gleiche Sache – CodingGorilla

Antwort

3

Sie können dies tun und keine Code-Änderungen an der Quelle erforderlich.

Sie können Reflektion verwenden, um eine Schein- oder abgeleitete Instanz zu injizieren.

Da System.Random nicht sealed ist, ist Ihre beste Wette hier, eine neue Klasse abzuleiten, und dann müssen Sie nur die gewünschten Methoden überschreiben.

Ich gehe davon aus, dass Sie eine Art von Unit-Test-Framework verwenden, so dass das folgende Beispiel dafür ist.

In Ihrer Unit-Test-Datei, dies zu tun:

class MyRandom : System.Random 
{ 
    public override double NextDouble() 
    {  
      // return your fake random double here. 
    } 
} 


class TestFixture 
{ 
    public void UnitTest() 
    { 
      // Create the unit under test 
      foo unit = new foo(); 

      // use reflection to inject a mock instance 
      typeof(foo) 
      .GetField("r", BindingFlags.Instance | BindingFlags.NonPublic) 
      .SetValue(unit, new MyRandom()); 

      // ACT: call method 
      var result = foo.bar(); 

      // examine results 
     } 
} 
+0

Wow .. das ist fast das, was ich brauche ... Ich frage mich, ob das auf privaten Feldern funktionieren wird ?? – Betamoo

+0

Ja, kein Problem. Es hängt davon ab, wie Sie die 'BindingFlags' angeben. –

+0

Wird dies überall funktionieren - d. H., Wenn ich eine Klasseninstanz von etwas im .NET-Framework erstelle und neue Random() verwendet, erhält es eine Instanz von MyRandom? – paulm

0

Werfen Sie einen Blick auf Dependency Injection. Es ist genau für diesen Umstand ausgelegt. Es gibt verschiedene DI/IoC-Frameworks für C#.

Einheit und Ninject sind beide gut, aber es gibt viele andere.

5

EDIT: Wie ChaosPandion darauf hinweist, wäre es besser, die Logik in der Methode zu testen, einen doppelten Parameter zu nehmen, dh double bar(double input) { ... }, aber wenn Sie einen Grund haben, Ihre eigene Quelle von Randoms zu injizieren, könnten Sie einen verwenden dieser Ansätze:

Sie bar ändern könnte Func<double> erhalten Werte von nehmen:

double bar(Func<double> source) 
{ 
    double ans = source(); 
    //evaluate ans 
    return ans; 
} 

Dann können Sie Ihre eigene Implementierung für Test injizieren, und im Hauptprogramm verwenden:

Random r = new Random(); 
double ans = bar(r.NextDouble); 

Alternativ könnten Sie Ihre eigenen IRandom Schnittstelle erstellen und dann einen Wrapper um die reale Random Klasse implementieren und zu verwenden spotten für die Prüfung:

interface IRandom 
{ 
    double Next(); 
} 

public class RandomWrapper : IRandom 
{ 
    private Random r = new Random(); 
    public double Next() 
    { 
     return this.r.NextDouble(); 
    } 
} 
+1

Ich wählte Sie, aber ich muss nur fragen, warum nicht ein Doppel als Parameter nehmen? – ChaosPandion

0

Sie benötigen eine Abstraktionsschicht hinzuzufügen.

Erstellen Sie eine Schnittstelle, die den Vertrag definiert, den Sie benötigen, um Ihre Random-Klassen zu unterstützen.

Erstellen Sie Implementierungen dieser Schnittstelle, von denen eine die ursprüngliche Random-Klasse verwenden sollte, die andere wäre Ihre "zufällige" Implementierung.

Stellen Sie die korrekte Implementierung wann und wo erforderlich bereit.

2

Ich glaube, dass Sie die Implementierung einer Klasse wie Random mit Tools wie JustMock oder TypeMock ersetzen können.

Ohne den ursprünglichen Code neu zu strukturieren, um Dependency Injection zu verwenden (oder anderweitig eine Abstraktionsschicht einzufügen), können Sie jedoch nicht das tun, was Sie bei der Verwendung von C# eingeben. Wenn Sie jedoch Ihren Code so umstellen, dass er harte Abhängigkeiten beseitigt, wird der Code sauberer und einfacher. Daher würde ich sagen, dass dies der bevorzugte Weg sein sollte, wenn Sie die erforderliche Umstrukturierung nicht wirklich durchführen können.

1

Sie könnten eine Klasse von System.Random ableiten und ihre Methoden überschreiben.

class PseudoRandom : System.Random 
{ 
    public override int Next() {...} 
    public override int Next (int maxValue) {...} 
    public override int Next (int minValue, int maxValue) {...} 
    public override void NextBytes (byte [] buffer) {...} 
    public override double NextDouble() {...} 
} 

Verwenden Sie diese Klasse zum Instanziieren von Main.

Random random = new PseudoRandom(); 

Wenn Sie später auf, um festzustellen, ob Sie die Systemklasse oder Ihre eigene abgeleitete Version verwenden, können Sie überprüfen, ob die „ist“ Operator. (Wahrscheinlich nicht das beste Design, aber es ist schnell und einfach)

// We need to see which class we're working with 
if (random is PseudoRandom) 
{ 
    // Using our custom class 
} 
else 
{ 
    // Using default System.Random class 
} 
+0

Gute Idee .. aber kann ich in C# sagen: "Jede Random-Klasse in dieser Datei ist MyNamespace.Random nicht System.Random" ?? – Betamoo

+0

Ja, überprüfen Sie mit dem is-Operator. Aktualisierte Antwort –

0

Antwort A, ein wenig verworren, aber es funktioniert

public class Foo 
{ 
    Func<double> next; 
    Foo(bool use_new) 
    { 
     if(use_new) 
      next = (new MyRandom()).NextDouble; 
     else 
      next = (new Random()).NextDouble;    
    } 
    void bar() 
    { 
     ans = next(); 
     ... 
    } 
} 

Antwort B, etwas klarer auf Vorsatz imho. Definieren Sie eine gemeinsame Schnittstelle, die eine Funktion zurückgibt. Wählen Sie dann einfach die zu verwendende Funktion.

public interface IRandomProvider 
{ 
    Func<double> NextDouble { get; } 
} 

public class StdRandomProvider : IRandomProvider 
{ 
    Random r = new Random(); 
    Func<double> NextDouble 
    { 
     get { return()=>r.NextDouble(); } 
    } 
} 

public class MyRandomProvider : IRandomProvider 
{ 
    MyRandom r = new MyRandom(); 
    Func<double> NextDouble 
    { 
     get { return()=>r.MyNextDouble(); } 
    } 
} 

public class Foo 
{ 
    IRandomProvider r; 
    Foo(bool use_new) 
    { 
     if(use_new) 
      r= new MyRandomProvider(); 
     else 
      r= new StdRandomProvider();    
    } 
    void bar() 
    { 
     ans = r.NextDouble(); 
     ... 
    } 
} 
+0

Crap Ich habe gerade festgestellt, dass Random nicht "versiegelt" ist und so mit der Antwort von AK geht – ja72

Verwandte Themen