• Noser.com
facebook
linkedin
twitter
youtube
  • NOSERmobile
    • Android
    • HTML 5
    • Hybrid Apps
    • iOS
    • Windows Phone
  • NOSERembedded
    • Medizintechnik
  • NOSERprojektmanagement
  • NOSERtesting
  • NOSERlifecycle
    • .NET Allgemein
    • Application Lifecylce Management
    • Apps
    • Architektur
    • ASP.NET
    • Azure
    • Cleancode
    • Cloud
    • Silverlight
    • Visual Studio / Team Foundation Server
    • Windows 8
    • Windows Presentation Foundation
  • NOSERinnovation
    • Big Data
    • Cloud
    • IoT
    • Operations Research
    • Augmented Reality
    • RFID, NFC, Bluetooth LE

Angular Forms

04. September 2019
Hans Arnet
0

Einleitung

Dieser Blog richtet sich an die Leserschaft, die bereits mit Angular vertraut sind, jedoch ihr Wissen über Forms vertiefen wollen. Wer über weniger Erfahrung verfügt, benutzt besser die detailliertere Schrit um Schritt Beschreibung mit den zur Verfügung gestellten Anwendungsbeispielen, welche hier zu finden sind.

 

Forms und Ihre Bedeutung

Views bzw. Forms übernehmen bei Web-Applikationen eine bedeutende Rolle. Sie verbinden Mensch und Maschine und sind dafür verantwortlich, dass deren Austausch von Informationen verständlich und fehlerfrei stattfindet. Die vom Benutzer eingegebenen Daten werden validiert und nur dann an die nächste Schicht übergeben, wenn sie fehlerfrei sind. Sind die Eingabedaten falsch, muss der Benutzer unmittelbar nach der Eingabe verständlich darüber informiert werden.

Typische Anforderungen an Forms sind zum Beispiel:

  • Die Daten müssen gemäss den Anforderungen vollständig und gültig sein, bevor sie versendet werden.
  • Eine möglichst genaue Beschreibung liefern, falls ein Fehler vorliegt. Eine Eingabe kann z.B. mehrere Fehlerursachen haben. z.B. dass eine Emailadresse nicht das gültige Muster einer Emailadresse aufweist, oder dass die eingetippte Emailadresse bei der Registrierung bereits verwendet wurde.
  • Abhängigkeiten zwischen den Controls müssen modellierbar sein. Zum Beispiel stellt eine Dropdownbox nur die Auswahl-Elemente zur Verfügung, die sich nach dem gewählten Wert eines anderen Eingabefeldes orientieren.
  • Regeln für das Anzeigen von Validierungsfehler befolgen. Soll z.B. während der Eingabe ein festgestellter Fehler angezeigt werden oder erst beim Sendeversuch der Daten? Soll der Sendebutton erst dann den Status «Enable» bekommen, wenn alle Daten gültig sind?)
  • Sicherheitsaspekte erfüllen, damit Hacker keine Grundlage geschaffen wird, um in ein System einzudringen.
  • Aspekte der Barrierefreiheit berücksichtigen.

 

Strategie der Validierung

Validierungsnachrichten  gleich nach der Eingabe anzeigen

Eingabedaten sollen unmittelbar nach ihrer Eingabe validiert und allfällige Eingabefehler dem Benutzer mitgeteilt werden. Nicht bediente Pflichtfelder oder fehlerhafte Felder sollen dazu führen, dass ein Formular nicht versendet wird. 

 

Der Button «Submit» kann durch den Benutzer erst dann betätigt werden, wenn die Daten gültig, vollständig und fehlerfrei sein.

 

Validierungsnachrichten erst beim Sendeversuch anzeigen

Oft werden bei Web-Applikationen Validierungsmitteilungen erst dann angezeigt, wenn der Sendebutton betätigt wird. Das trifft meistens dann zu, wenn die Daten serverseitig geprüft werden. Diese Strategie ist weniger benutzerfreundlich und sollte deshalb nicht angewendet werden.

Zeitpunkt der Validierungsnachrichten bestimmen

Wann der Validierungszeitpunkt stattfinden soll, kann über  die ngFormOptions bestimmt werden. Mehr dazu in den untenstehenden Kapiteln.

Technologien

Angular verfolgt zur Erstellung von Formularen zwei Techniken. Model-Driven (reaktiver Ansatz) oder Template-Driven:

Template-Driven

 Die Funtionsweise

 

Template-Driven benötigt das Model nicht, die Definitionen werden deklarativ im HTML vorgenommen. Dabei kommt das Forms-API zum Tragen, welches im Hauptmodul mittels FormsModule wie folgt importiert und registriert wird.

 

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { <span style="color: #0000ff;">FormsModule</span>, ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { ReaktivFormComponent } from './view/reaktiv-form/reaktiv-form.component';
import { TemplateFormComponent } from './view/template-form/template-form.component';
import { UniqueEmailValidatorDirective } from './directiven/emailUnique.directive';


@NgModule({
  declarations: [
    AppComponent,
    ReaktivFormComponent,
    TemplateFormComponent,
    UniqueEmailValidatorDirective
  ],
  imports: [
    BrowserModule,
      AppRoutingModule,
      <strong><span style="color: #0000ff;">FormsModule</span></strong>,
    ReactiveFormsModule,
    HttpClientModule,
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Dadurch wird die direktive NgForm erzeugt und kann wie im Beispiel der lokalen Variable «cutomerForm» zugewiesen werden. (Achtung, diese Variable ist nur innerhalb des Templates gültig).
<div class="container">
  <div class="row">
    <div class="col-md-6 offset-md-3">
      <h1>Template-Driven Form</h1>
      <form novalidate #customerForm="ngForm" [ngFormOptions]="{ updateOn: 'blur' }">
        <div [hidden]="customerForm.submitted">
          <div class="form-group">
            <label for="email">Email:</label>
            <input id="email" name="email" class="form-control" appEmailValidator [(ngModel)]="Email" #email="ngModel">
            <div *ngIf="email.invalid && (email.dirty || email.touched)" class="alert alert-danger">
              <div *ngIf="email.errors?.EmailValidatorMsg">
                {{email.errors.EmailValidatorMsg}}
              </div>
            </div>
          </div>

. . .

Dies führt dazu, dass Properties bereitgestellt werden, die den Kontrollstatus und die Gültigkeit des Forms verfolgen. Folgende Properties und Methoden stehen zur Verfügung:

Properties Methode Bedeutung
submitted Gibt zurück, ob eine Form-Übermittlung ausgelöst wurde
errors Gibt an, ob ein Form fehlerhaft ist. Tritt mindestens ein Fehler in einem Eingabefeld auf, hat die Variable den Wert «true»
touched Gibt an, ob ein Eingabefeld den Fokus erhielt oder ein Mouse-Event ausgelöst wurde.
dirty Wird ein Wert in einem Eingabefeld nach der Eingabe verändert, hat die Variable «dirty» den Wert true
resetForm() Alle Eingabefelder werden zurückgesetzt.
controls Gibt ein Instanz der FormControl aus der FormGroup zurück.

 

Folgendes Beispiel zeigt, wie die Properties von ngForm angewendet werden:

 

 

<button type="submit" class="btn btn-default"
     [disabled]="customerForm.invalid">
     Senden
</button>

 

 

Der Button «Senden” wird disabled dargestellt, falls das Formular fehlerhafte Daten hat. In diesem konkreten Fall wird das HTML-Property «disabled» an die Variable «customerForm.invalid» gebunden.  Eine Änderung dieses Variablenwertes wird somit gleich an dieses HTML-Property übertragen. Sobald das Formular valide ist, wird auch der Button enabled dargestellt. Dies geschieht über das One-Way-Binding.

Eine weitere Möglichkeit zur Validierung bieten Direktiven. Folgende wird hier für die Prüfung bereits registrierter Emailadressen verwendet

Der Einsatz von Direktiven für Validierungszwecke

import { Directive, forwardRef, Injectable } from '@angular/core';
import { of, Observable } from 'rxjs';
import { EmailService } from '../service/email.service';
import {
    AsyncValidator,
    AbstractControl,
    NG_ASYNC_VALIDATORS,
    ValidationErrors
} from '@angular/forms';
import { catchError, map } from 'rxjs/operators';


@Injectable({ providedIn: 'root' })
export class UniqueEmailValidator implements AsyncValidator {
  constructor(private emailService: EmailService) {

  }

  validate(
    ctrl: AbstractControl
  ): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> {
    console.log('in Validation');
    return this.emailService.isEmailAlreadyRegistered(ctrl.value).pipe(
      map(isTaken => (isTaken ? { uniqueEmail: true } : null)),
      catchError(() => null)
    );
  }
}

@Directive({
    selector: '[appUniqueEmail]',
    providers: [
        {
            provide: NG_ASYNC_VALIDATORS,
            useExisting: forwardRef(() => UniqueEmailValidator),
            multi: true
        }
    ]
})


export class UniqueEmailValidatorDirective {
    constructor(private validator: UniqueEmailValidator) { }

    validate(control: AbstractControl) {
        this.validator.validate(control);
    }
}

und wird im Template wie folgt integriert.
<div class="form-group">
  <label for="email">Email</label>
  <input id="email" class="form-control" name="email"
          #email="ngModel"
          [(ngModel)]="customer.email"
          [ngModelOptions]="{ updateOn: 'blur' }"
          appUniqueEmail>
  <div *ngIf="email.pending">Validating...</div>
  <div *ngIf="email.invalid" class="alert alert-danger">
    email is already taken.
  </div>
</div>

In dieser Direktive wird ein Http-Service verwendet, damit in einem externen Server die Prüfung durchgeführt werden kann, ob eine Emailadresse für eine Registrierung bereits schon verwendet wurde.
import { Injectable } from '@angular/core';
import { Observable, of, throwError } from 'rxjs';
import { delay, catchError, retry, map } from 'rxjs/operators';
import { HttpClient, HttpHeaders, HttpErrorResponse, HttpParams } from '@angular/common/http';
import { tap } from 'rxjs/internal/operators';



@Injectable({ providedIn: 'root' })
export class EmailService {

  constructor(private http: HttpClient) { }

  isEmailAlreadyRegistered(email: string): Observable<boolean> {

    const options = email ?
      { params: new HttpParams().set('email', email) } : { headers: new HttpHeaders().set('Content-Type', 'application/json') };

    return this.http.get('http://localhost:60490/RegisteredEmail', options).pipe(map(res => { console.log(res); return res == "1" ? true : false }));
  }

}

Da die Anbindung über einen externen Server erfolgt, erhalten wir das Prüfergebnis asynchron und verwenden deshalb in der  Directive das NG_ASYNC_VALIDATORS-Token. Pipe bietet hier die Möglichkeiten, den Operator map auf die Werte des Streams anzuwenden, um diesen in einer veränderten Form dem Stream wieder zur Verfügung zu stellen. Konkret, mit dem Operator map wird der Boolean aus dem Stream in ein ValidationErrors-Objekt transformiert und an den Stream weiter geleitet.

Wann übrigens eine Validierung stattfinden soll, kann im Template mittels ngModelOptions im Property UpdateOn bestimmt werden. Folgende Möglichkeiten stehen zur Verfügung:

Property Bedeutung
Blur Validierung erfolgt, wenn der Benutzer das Eingabefeld verlässt.
update Bei jeder Veränderung des Wertes im Eingabefeld, oder beim Eintippen eines Wertes nach jedem Buchstaben.
submit Zum Zeitpunkt, wenn das Formular abgeschickt wird.

Übrigens lohnt es sich, den Decorator einer Directive genauer unter die Lupe zu nehmen. Hier bestehen Möglichkeiten, das Verhalten und weitere Eigenschaften zu definieren. Z.B. auf welche Formulare eine Directive angewendet werden soll etc.

 

Conclusion

Bei der Anwendung von Template-Driven entfällt viel Implementationsaufwand und oft ist diese Methode hinreichend, um übliche Anforderungen zu erfüllen. Wie auch im konkreten Fall, wo ein Http-Service verwendet wird. Ein durchaus grosser Vorteil dieser Methode ist, dass Validierungen angepasst werden können, ohne dabei eine Zeile Typescript hinzuzufügen. Dies kann unkompliziert in der Form erweitert werden. Jedoch übersteigt die Projektgrösse ein bestimmtes Mass, führt diese Methode zu einer schlechteren Übersicht. Zudem verkompliziert sich bei kombinierten Anwendungen die Integration von synchronen und asynchronen Anwendungsblöcken.

Reaktive-Driven

 Die Funtionsweise

Bei Reaktiven Template steht das Model im Vordergrund. Auch hier kann im Template eine lokale Variable verwendet werden, welche ihre Daten aus dem Model bezieht.  Dies geschieht nicht wie bei Template-Driven automatisch, sondern wird über den Konstruktor in das Model injected. Um diese Technologie anzuwenden, muss im Hauptmodule (app.module.ts) ReactiveFormsModule hinzugefügt werden.

Im Modell wird die FormGroup instanziert. Sie dient als Bindeglied zum Formular.

import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroup, FormBuilder, Validators } from '@angular/forms';
import { EmailService } from '../../service/email.service';
import { UniqueEmailValidatorDirective, UniqueEmailValidator } from '../../directiven/emailUnique.directive';

@Component({
  selector: 'app-reaktiv-form',
  templateUrl: './reaktiv-form.component.html',
  styleUrls: ['./reaktiv-form.component.css']
})

export class ReaktivFormComponent { 

  genders = ['Herr', 'Frau'];
  customer = { firstname: 'Hans', email: 'hans.muster@bluewin.ch', lastname: 'Muster', gender: this.genders[0] };
  customerForm: FormGroup;



  constructor(private fb: FormBuilder, private emailService: EmailService, private uniqueEmailValidator: UniqueEmailValidator) {


    this.customerForm = fb.group({
      email: [this.customer.email,
        [Validators.required, Validators.email],
        this.uniqueEmailValidator.validate.bind(this.uniqueEmailValidator)
      ],
      gender: [this.customer.gender, [Validators.required]],
      firstname: [this.customer.firstname, [Validators.required, Validators.minLength(5)]],
      lastname: [this.customer.lastname, [Validators.required]],
    })
  }

    get firstname() { return this.customerForm.get('firstname'); }
 
    get lastname() { return this.customerForm.get('lastname'); }

    get email() { return this.customerForm.get('email'); }

    get gender() { return this.customerForm.get('gender'); }
}

reaktiv-form.component.ts

 

Die durch NgForm instantierte  FormGroup der obersten Ebene verbindet das Formular, um den aggregierten Formularwert und den Überprüfungsstatus – wie zuvor beschrieben – zu verfolgen.
customerFormGroup stellt somit für jedes Feld ein FormControl mit den gewünschten Validierungs- und Statiprüfungen zur Verfügung. Somit aggregiert Formgroup den Zustand von allen FormControls. Ist ein FormControl nicht valide, ist auch FormGroup nicht valide. 

. Dabei sieht der Aufbau im Formular wie folgt aus:

<h1>Reactive Form</h1>
<form novalidate [formGroup]="customerForm">


  <div class="form-group">
    <label for="gender">Gender:</label>
    <select id="gender" class="form-control"
            formControlName="gender" required>
      <option *ngFor="let p of genders" [value]="p">{{p}}</option>
    </select>
    <div *ngIf="gender.invalid && gender.touched" class="alert alert-danger">
      <div *ngIf="gender.errors.required">Gender is required.</div>
    </div>
  </div>



  <div class="form-group">
    <label for="email">Email:</label>
    <input type="email" id="email" class="form-control"
           formControlName="email">
    <div *ngIf="email.pending">Validating...</div>
    <div *ngIf="email.invalid" class="alert alert-danger email-errors">
      <div *ngIf="email.errors?.uniqueEmail==true">Already registered email</div>
      <div *ngIf="email.errors?.minlength">email must be at least 4 characters long.</div>
      <div *ngIf="email.errors?.email">does not have the pattern of an email address.</div>
      <div *ngIf="email.errors.required">Email name is required.</div>

      <!--Alternative Möglichkeit, auf das Model zurück zu greifen-->
      <div *ngIf="customerForm.get('email').errors && customerForm.get('email').errors.uniqueEmail">
        Already registered!!
      </div>
    </div>
  </div>

  <div class="form-group">
    <label for="firstname">First name:</label>
    <input id="firstname" class="form-control"
           formControlName="firstname" required>
    <div *ngIf="firstname.invalid && (firstname.dirty || firstname.touched)"
         class="alert alert-danger">
      <div *ngIf="firstname.errors.required">
        First name is required.
      </div>
      <div *ngIf="firstname.errors.minlength">
        First name must be at least 4 characters long.
      </div>
    </div>
  </div>



  <div class="form-group">
    <label for="lastname">Last name:</label>
    <input id="lastname" class="form-control"
           formControlName="lastname">
    <div *ngIf="lastname.pending">be patient, we check your data...</div>
    <div *ngIf="lastname.invalid" class="alert alert-danger last-name-errors">
      <div *ngIf="lastname.errors.required">
        Last name is required.
      </div>
      <div *ngIf="lastname.errors.minlength">
        Last name must be at least 4 characters long.
      </div>
    </div>
  </div>



  <button type="submit" class="btn btn-default"
          [disabled]="customerForm.invalid">
    Submit
  </button>

</form>

Im Model muss nur noch ein Getter wie folgt zur Verfügung gestellt werden,
   get email() { return this.customerForm.get('email'); }

um auf das entsprechende Objekt im Form zuzugreifen.
<div *ngIf="lastname.invalid" class="alert alert-danger last-name-errors">

Auf diesen  Getter kann verzichtet, jedoch müsste aber folgender Schreibweise angewendet werden.
      <div *ngIf="customerForm.get('email').errors && customerForm.get('email').errors.uniqueEmail">
        Already registered!!
      </div>

Dadurch wird jedoch das Form eher unleserlich.

 

Die Klasse FormGroup

Zentral von Bedeutung bei der Validierung von Daten ist beim reaktiven Modell die Klasse Formgroup. Wie erwähnt, Formgroup ist das Bindeglied zu ngForm-Direktive und aggregiert sämtliche zugewiesenen Formcontrols. (siehe reaktiv-form.component.ts).
Im Überblick sind hier die wichtigsten Properties und Methoden aufgeführt.

Properties/Methode Bedeutung
submitted Gibt zurück, ob eine Form-Übermittlung ausgelöst wurde
errors Gibt an, ob ein Form fehlerhaft ist. Tritt mindestens ein Fehler in einem Eingabefeld auf, hat die Variable den Wert «true»
touched Gibt an, ob ein Eingabefeld den Fokus erhielt oder ein Mouse-Event ausgelöst wurde.
dirty Wird ein Wert in einem Eingabefeld nach der Eingabe verändert, hat die Variable «dirty» den Wert true
resetForm() Alle Eingabefelder werden zurückgesetzt.
controls Gibt ein Instanz der FormControl aus der FormGroup zurück.

Weitere detaillierte Infos sind hier zu finden.

 

Conclusion

Die Technologie Reactive-Forms wirkt aus meiner Sicht übersichtlicher und stellt für aufwendigere Validierungen die bessere Lösung dar. Dieser Ansatz kommt vor allem dann zum Tragen, wenn Forms dynamisch geladen werden oder eine Applikation mehrsprachig funktionieren muss.

Solid Prinzip

23. November 2018
Hans Arnet
1

Das Akronym “SOLID” beschreibt Prinzipien, die beim Entwurf eines objektorientierten Designs zu berücksichtigen sind. Durch das Einhalten dieser Prinzipien werden entscheidend wichtige Qualitätsmerkmale in der Softwareentwicklung wie Stabilität, Wartbarkeit, Testbarkeit, Erweiterbarkeit etc. erreicht. Dieses Prinzipien kann sowohl auf Klassen wie auch auf Komponenten angewendet werden.

 

Wie definiert sich Solid?

Solid präsentiert sich mittels folgenden Prinzipien:

S

Single-Responsibility-Prinzip

Das Single-Responsibility-Prinzip besagt, dass jede Klasse nur eine einzige Verantwortung haben soll. Sie sollen die Verantwortung über bestimmte abgegrenzte Aktivitäten beinhalten und nur eine bestimmte Menge an Informationen haben.

Ein Beispiel liefert die Klasse Person, die nebst Angaben zu einer Person eine Funktion einer AHV-Nr.-Validierung beinhaltet. Hier treffen wir auf zwei unterschiedliche Verantwortlichkeiten Erstens: Alles was zu einer Person gehört Zweitens: Die AHV-Nr.-Validierung, welche nicht zum Aufgabenbereich dieser Klasse gehört, obwohl eine Person eine AHV-Nummer besitzt. Diese Verantwortlichkeiten müssen getrennt werden. Je besser sich eine Klasse auf eine Verantwortung oder Aufgabe konzentriert, je höher ist ihre Kohäsion*

Bei diesem Prinzip ist auch dieser Grundsatz einzuhalten:
“Es sollte nie mehr als einen Grund dafür geben, eine Klasse zu ändern.“ Wenn in einer Klasse nur Funktionen vorhanden sind, die zur Erfüllung einer Aufgabe beitragen, sollte es auch nicht mehr als einen Grund geben, sie zu ändern.

O

Open/closed Prinzip

Komponenten sind so zu bauen, dass Erweiterungen ohne Änderungen des bestehenden Quellcodes möglich sind. Schnittstelle und Verhalten dürfen sich nicht ändern. Änderungen und Erweiterungen erfolgen in neuem Code.

Hier ein Beispiel einer Log-Komponente, welches dieses Prinzip berücksichtigt:

1.) Definieren einer Schnittstelle

interface ILogger
{
    void Log(string message);
}

2.) Erstellen der Klasse ConsoleLogger, welche Logs auf dem Bildschirm aus gibt. Diese Klasse implementiert die Schnittstelle ILogger.
class ConsoleLogger : ILogger
{
    public void Log(string message)
    {
        Console.WriteLine(message);
    }
}

3.) Erstellen der Klasse LogEngine, welche ebenfalls ILogger implementiert. Über den Konstruktor wird eine Instanz der Klasse ConsoleLogger zur Verfügung gestellt.
class LogEngine : ILogger
{
    private ILogger _logger = null;

    public LogEngine(ILogger logger)
    {
        this._logger = logger;
    }


    public void Log(string message)
    {
        _logger.Log(message);
    }
}

Dieser Klasse LogEngine können beliebige Implementationen übergeben werden, indem diese Objekte im Konstruktor injiziert* werden. Kommt z.B. eine neue Anforderung hinzu, dass z.B. die Software Log-Informationen in die Datenbank schreiben soll, muss am bestehenden Code nichts geändert werden. Es benötigt nur eine neue Klasse, welche  die Schnittstelle ILogger implementiert und dafür sorgt, dass die Daten in die Datenbank geschrieben werden. Dies könnte dann wie folgt aussehen:
class DbLogger : ILogger
{
    public void Log(string message)
    {
        using (var context = new myModel())
        {
            context.Log.Add(new Log() { Message = message });
            context.SaveChanges();
        }
    }
}

Download executable

In der Praxis treffen wir oft auf Beispiele wie Folgendes, welches gegen dieses Prinzip verstösst, weil der bestehende Code verändert wird.

public void WriteLog(Log log)
{
    if (log.Type = consoleLog)
    {
        WriteToConsole(log)
    }
    else if (logType.Type = fileLog)
    {
        WriteToFile(log)
    }
    //Um folgendes "else if" wird der Code erweitert.
    //Änderungen am bestehenden Code sind vermeiden!
    else if (logType.Type = fileDB)
    {
        WriteToDB(log)
    }
}
L

Liskovsches Substitutionsprinzip

Das Liskovsches Substitutionsprinzip (LSP) oder Ersetzbarkeitsprinzip fordert, dass eine Instanz einer abgeleiteten Klasse sich so verhalten muss, dass jemand, der meint, ein Objekt der Basisklasse vor sich zu haben, nicht durch unerwartetes Verhalten überrascht wird, wenn es sich dabei tatsächlich um ein Objekt eines Subtyps handelt.

Hier ein Beispiel, welches gegen dieses Prinzip verstosst: Es beseht die Superklasse wie folgt:

public class Rectangle
{
     private int Height { get; set; }

     private int Width { get; set; }

     void ChangeHeight(int height)
     {
         Height = height;
     }

     void ChangeWidth(int width)
     {
         Width = width;
     }
}

Nun wird eine neue Klasse Square benötig. Diese wird von der Klasse Rectangle abgeleitet.
public class Square : Rectangle
{
   …
  

Somit stellt die Klasse Square die Methoden ChangeHeight und ChangeWidth zur Verfügung. Aber was geschieht, wenn diese beiden Funktionen aufgerufen werden? Dann wird eine Seite es Quadrat verändert und folglich ist das Objekt dann kein Quadrat mehr, sondern ein Rechteck. Die Methode ChangeHeight  und ChangeWidth verhalten sich anders als erwartet.
I

Interface-Segregation-Prinzip

Das Interface-Segregation-Prinzip oder Schnittstellenaufteilungsprinzip besagt, dass grosse Schnittstellen in mehrere kleinere aufzuteilen sind, damit die zu implementierenden Klassen nicht unnötige Methoden beinhalten.

Betrachten wir dies am Beispiel der CRUD-Operationen. Je nach Rolle ist es nur erlaubt, nur Daten zu lesen. andere rollen dürfen nebst Lesen auch Daten ändern oder hinzufügen.  Damit gäbe es für unterschiedliche Rollen unterschiedliche Interfaces. So könnte es z.B. ein Interface geben, das nur das Lesen von Daten erlaubt und eines, das zusätzlich die Modifikation erlaubt. So kann beim Lesen von Daten auf IRead zugegriffen werden – mit einem weit schlankeren Interface.

Hier ein Beispiel, was die Folgen davon sind, wenn Schnittstellen nicht optimal definiert sind:

Da die Klasse Repository das Interface IDataOperation implementiert, jedoch nur lesender Datenzugriff erlaubt ist, muss demzufolge diese Klasse die Methode Add, Edit und Delete trotzdem beinhalten. Demzufolge werden diese Methoden wie im folgenden Beispiel zur Verfügung gestellt, jedoch nicht mit einer sinnvollen Implementierung bzw. so,  dass bei der Verwendung eine Exception geworfen wird.

Im folgenden Beispiel wird die beschriebene Problematik verdeutlicht:

    class Repository : IDataOperation
    {
        public Person Read(int? id)
        {
            using (var db = new MyContextDB())
            {
                return result = db.Person.SingleOrDefault(b => b.id == id);
            }
        }

        public void Add(Person person)
        {
            throw new NotSupportedException();
        }

        public void Delete(int? id)
        {
            throw new NotSupportedException();
        }

        public void Edit(int? id)
        {
            throw new NotSupportedException();
        }

    }

Finden Sie also NotImplemtedException bzw. NotSupportedException im Code, könnte dies ein Indiz für die Verletzung des Interface-Segregation-Prinzip sein.

 

 

Besser zu lösen wäre die obige Aufgabe gemäss folgenden Beispiel, indem wir Schnittstellen verwenden, welche zu den erwartenden Aufgaben einer Klasse gehören.

    public class Repository : IRead, IModify
    {
        public void Modify(Person person)
        {
            using (var db = new MyContextDB())
            {
                var result = db.Person.SingleOrDefault(b => b.id == id);
                if (result != null)
                {
                    result.SomeValue = "Some other value";
                    db.SaveChanges();
                }
            }
        }

        public Person Read(int? id)
        {
            using (var db = new MyContextDB())
            {
                return result = db.Person.SingleOrDefault(b => b.id == id);
            }
        }
    }

 
D

Dependency-Inversion-Prinzip

Das Dependency Inversion Principle (DIP) beschäftigt sich mit der Abhängigkeit von Modulen. Im Allgemeinen wird das DIP wie folgt beschrieben:Module höherer Ebenen sollten nicht von Modulen niedrigerer Ebenen abhängen. Beide sollten von Abstraktionen abhängen. Abstraktionen sollten nicht von Details abhängen. Details sollten von Abstraktionen abhängen.Hier ein Beispiel, welches diesem Prinzip gerecht wird:1.) Definieren einer Schnittstelle

public interface ILight{
    void On();
    void Off();
}

2.) Implementieren ILight
public class Light : ILight
{
    private bool IsOn = false;
    public void Off()
    {
        IsOn = false;
    }

    public void On()
    {
        IsOn = true;
    }
}

3.) Implemetieren Switch
public class Switch
{
    private ILight switchClient;
    private Boolean switchClientPressed;


    public Switch(ILight switchClient)
    {
        this.switchClient = switchClient;
    }


    public void Press()
    {
        switchClientPressed = !switchClientPressed;
        if (switchClientPressed)
        {
            this.switchClient.Off();
        }
        else
        {
            this.switchClient.On();
        }
    }
}

Download Executable

In diesem Beispiel wird der Klasse Switch einfach eine Instanz der Klasse Light im Konstruktor mitgegeben. Switch stellt nur Methoden  wie z.B. Press zur Verfügung, welches mit der Methoden des übergebenen Objektes interagiert. Dadurch werden Abhängigkeiten verhindert.

Hier ein Beispiel, welches gegen dieses Prinzip verstösst :

public class Switch
{
    Light light = new Light();

    public void Press()
    {
            
        if (this.light.IsOn)
        {
            this.light.Off();
        }
        else
        {
            this.light.On();
        }
    }

}

In der Klasse Switch wird die Klasse Light instanziert. Dadurch erzeugen wir eine Abhängigkeit zwischen diesen beiden Klassen.

Grundsätzlich kann gesagt werden, “wenn in einer Klasse eine andere Klasse instanziert wird, entsteht eine Abhängigkeit”.

 

Kohäsion*

Kohäsion ist ein Mass , wie gut eine Aufgabe bzw. eine Verantwortung mittels Software abgebildet wird. Kümmert sich eine Klasse nur um eine Aufgabe (Single-Responsibility-Prinzip ist erfüllt), so hat sie eine sehr hohe Kohäsion. Kümmert sie sich um viele verschiedene, nicht zusammengehörige Aufgaben, so hat sie eine geringe Kohäsion. Manager-Klassen haben typsicherweise eine sehr geringe Kohäsion.

 

Injiziert*

Eine Technik, wo ein Objekt über den Konstruktor eines anderen Objektes übergeben bzw. injiziert wird. Diese Technik wird bei Dependency Injection angewendet.

12

Tag Cloud

.NET android ANgular Angular Js AngularJs Arduino ASP.Net automated testing Azure Big Data C# C++ Cloud continuous integration Elm Embedded gRPC Internet of Things IoT Java Javascript M2M OWASP Projektmanagement protobuf Python Raspberry Pi Reactive Programming REST Scrum Security Softwarequalität SPA Testen testing Testmanagement Teststrategie Visual Studio WebAPI windows windows phone 8 WPF Xamarin Xamarin.Android Xamarin.Forms

Archive

Current Posts

  • Lasttests mit NBomber
  • Kreative Interaktion mit Xamarin.Forms Touch-Actions
  • Angular vs. React: Ein pragmatischer Vergleich
  • Eine Alternative zu Azure Time Series Insights, bitte?
  • Focus on the Secure Storage service of Trusted Firmware (TFM)

Last Comments

  • Noser Blog Touch-Actions in Xamarin.Forms - Noser Blog bei Mach mehr aus Animationen in Xamarin.Forms mit SkiaSharp
  • Noser Blog Focus on the Secure Storage service of Trusted Firmware (TFM) - Noser Blog bei First run of the Trusted Firmware (TFM) application
  • Noser Blog First run of the Trusted Firmware (TFM) application - Noser Blog bei Focus on the Secure Storage service of Trusted Firmware (TFM)
  • Noser Blog Focus on the Secure Storage service of Trusted Firmware (TFM) - Noser Blog bei Security management with Trusted Firmware
  • Noser Blog ZeroMQ / NetMQ mit Protocobuf - Noser Blog bei Tutorial: Protocol Buffers in ASP.NET Core

Popular Posts

Xamarin.Android Code Obfuscation

6 Comments

ManuScripts: Wenn jemand eine Reise tut... Funktionale Programmierung mit Elm - Teil 1 - Aufbruch

5 Comments

ManuScripts: Wenn jemand eine Reise tut... Funktionale Programmierung mit Elm - Teil 2 - Kein Picknick

4 Comments

Contact us

  1. Name *
    * Please enter your name
  2. Email *
    * Please enter a valid email address
  3. Message *
    * Please enter message
© 2013 NOSER ENGINEERING AG. All rights reserved. Datenschutz | Cookie-Richtlinie