Patrick Richert

.NET Development, C# and more...

EBC Calculator und Unit Tests

Ich habe mich neulich mit Event Based Components (EBC) beschäftigt, es ist eine nachrichtenorientierte Kommunikation zwischen Komponenten. Man kann sich EBCs auch als Platinen und Bausteine vorstellen, dabei werden die Bausteine mit der Platine verbunden.
So hätte man bei einem Taschenrechner ein Bauteil Addition, welches ein Eingangs-(Input) und Ausgangssignal (Output) enthält. Innerhalb des Bausteins wird dann die Rechenoperation ausgeführt und anschließend über das Ausgangssignal gesendet.

Und so würde der Baustein in Codeform aussehen:

 

public void Add(int a, int b)
{
	int result = (a + b);
	
	AddResult.Invoke(result);
}
public event Action AddResult;

 

Das tolle daran ist, dass die Komponenten keine Abhängigkeiten haben und sich somit besser testen lassen!


In meinem folgendem EBC_Calculator Beispiel habe ich eine WPF UI mit den Grundrechenoperationen erstellt.

 

Die Klasse Calculator aus der Components Assembly enthält die Rechenoperationen. Im Testprojekt wird die Klasse getestet.

In der Calculator habe ich nun folgende Operationen definiert:

 

 

Der Eingang eines EBCs ist immer eine Funktion und als Ausgang wird ein Action<T> definiert. Falls keine Parameter übergeben werden sollen, kann auch ein einfaches Action verwendet werden.
Nun haben wir die EBCs erstellt, aber diese sind noch nicht mit der Platine verbunden! Als Platine verwende ich die MainWindow.xaml.cs Klasse von der UI. Um jeweils ein Eingangs Input anzusprechen, werden auf der Platine weitere Action<T> mit den Übergabeparametern definiert.

 

 

Im Konstruktor werden die Ein- und Ausgänge der EBCs mit der Platine verbunden (BIND)

 

 

Nun müssen die jeweiligen Aktionen vom Benutzer ausgelöst werde, z.B. durch ein Button Click:

 

 

Hier werden die Eingaben an die Addition übergeben, welches dann die AddResult aufruft. Durch die Verdrahtung im Konstruktor wird festgelegt, welche Methode aufgerufen werden soll, wenn das Ergebnis übergeben wird. So wird anschließend die OnCalculatorResult aufgerufen.

 

 

 

 

Wie man sieht ist es anfangs gewöhnungsbedürftig, vor allem die Verdrahtung der Ein- und Ausgänge mit der Platine. Aber nach einigen Verdrahtungen hat man es drauf :-)

Wie schon erwähnt lassen sie EBCs einfacher testen, da sie keine Abhängigkeiten haben. In meinem Testprojekt werden alle Operationen getestet, wie z.B. die AddTest() Methode:

 

 

Jetzt nur noch den Test Runner laufen lassen...

 

Ich hoffe ich konnte euch einen kleinen Einblick in die EBC Welt verschaffen. Dann noch ein Happy BINDing Smile

 

DOWNLOAD

EBC_Calculator.zip (23,83 kb) [Downloads: 577]

Demoanwendung mit LightCore

In Anwendungen mit mehrschichtigen Architekturen ist es wichtig, dass es wenig Abhängigkeiten von anderen Komponenten gibt. Um solche Abhängigkeiten zu vermeiden bzw. zu verringern, gibt es sogenannte Inversion of Control Container (IoC Container). Mehr zum Thema IoC Container findet ihr hier


Nun möchte ich euch gerne den IoC Container "LightCore" von Peter Bucher anhand einer Demoanwendung demonstrieren.


Die LightCore.Demo Anwendung ist eine Windows Forms Applikation, die Kundendaten an ein DataGridView binden soll. Nun habe ich zum Projekt mehrere Komponenten hinzugefügt:

Core
:
Besteht aus dem LightCore IoC Container, welcher vom ModulManager verwaltet wird. Zusätzlich werden die Schnittstellen
ICustomerService, ICustomerRepository für den Business- und DataLayer definiert.

Framework:
Hat eine
Initializer Klasse, die alle Module beim ModulManager registriert, diese wird einmalig von der UI aufgerufen.

BusinessLayer:
Enthält ein CustomerService

DataLayer:
Enthält ein CustomerRepository, sowie ein MockCustomerRepository für den Zugriff auf Mockdaten.


Solution Explorer

 

 

Architektur

Wie man sehen kann, hat der BusinessLayer (BL) keine direkte Verbindung mit dem DataLayer (DL). Doch wie soll der CustomerService an die Daten kommen?
Dies geschieht durch Dependency Injection, dabei wird dem Konstruktor vom CustomerService ein ICustomerRepository "injiziert".

 

public class CustomerService : ICustomerService
{
	private ICustomerRepository<ICustomerData> _repository;

	public CustomerService(ICustomerRepository<ICustomerData> repository)
	{
		_repository = repository;
	}

	public IList<ICustomerData> GetAllCustomer()
	{
		return _repository.GetAllCustomer();
	}
}

 

 

Kommen wir nun zur Registrierung der Module mit dem IoC Container.
Sobald die UI startet, wird der Initializer aufgerufen, welcher dann die Module beim IoC Container registriert. Hierbei muss das Interface und die Implementierung beim Container registriert werden.

 

ModulManager.Register<ICustomerService, CustomerService>();

 

 

Ein netter Vorteil dabei ist, es lässt sich mit einer Codezeile eine andere Implementierung registrieren, so kann man jederzeit eine Repository durch eine MockRepository austauschen. So hab ich die CustomerRepository welche noch keine Implementierung hat, durch die MockCustomerRepository ausgetauscht.
Nachdem alle Komponenten registriert wurden, muss der IoC Container erstellt werden, dies geschieht mit Build()

Initializer

public class Initializer
{
	public static void Initialize()
	{
		ModulManager.Register<ICustomerRepository<ICustomerData>, MockCustomerRepository>();
		//ModulManager.Register<ICustomerRepository<ICustomerData>, CustomerRepository>();

		ModulManager.Register<ICustomerService, CustomerService>();

		ModulManager.Build();
	}
}

 

ModulManager

using LightCore;

namespace Core
{
    public class ModulManager
    {
        private static readonly ContainerBuilder _builder;
        private static IContainer _container;

        static ModulManager()
        {
            _builder = new ContainerBuilder();
        }

        public static void Register<TContract, TImplementation>() where TImplementation : TContract
        {
            _builder.Register<TContract, TImplementation>();
        }

        public static T Get<T>()
        {
            return _container.Resolve<T>();
        }

        public static void Build()
        {
            _container = _builder.Build();
        }
    }
}

 

 

In der UI soll auf eine Instanz des CustomerService zugegriffen werden, um die Kundendaten an das DataGridView zu binden. Hierbei arbeitet man nur gegen Interfaces, so wird ein ICustomerService deklariert, welcher dann vom ModulManager eine Instanz erhält.

 

ICustomerService service = ModulManager.Get<ICustomerService>();

dataGridView1.DataSource = service.GetAllCustomer();
dataGridView1.AutoGenerateColumns = true;


Und so sieht dann unsere UI aus, nachdem die Mockdaten an das DataGridView gebunden wurden...


 

 

 

DOWNLOAD

LightCore.Demo.zip (79,81 kb) [Downloads: 591]