navigate_before
Wróć do strony głównej
Rozwijanie umiejętności programowania obiektowego w TypeScript

Rozwijanie umiejętności programowania obiektowego w TypeScript

Rozwijanie umiejętności programowania obiektowego w TypeScript

Programowanie obiektowe jest jednym z najważniejszych paradigmatów programowania, który pozwala na tworzenie bardziej elastycznych i skalowalnych aplikacji. TypeScript, będący nadzbiorem języka JavaScript, oferuje wsparcie dla programowania obiektowego poprzez wprowadzenie koncepcji klas, interfejsów i dziedziczenia.

W celu rozwijania umiejętności programowania obiektowego w TypeScript warto zacząć od zapoznania się z podstawowymi konceptami tego paradygmatu. Klasy są fundamentalnym elementem programowania obiektowego, pozwalającym na definiowanie nowych typów oraz tworzenie instancji tych typów. W TypeScript deklaracja klasy wygląda następująco:

class Animal {
  name: string;

  constructor(name: string) {
    this.name = name;
  }

  move(distanceInMeters: number = 0) {
    console.log(`${this.name} moved ${distanceInMeters}m.`);
  }
}

Przykład ten przedstawia prostą klasę Animal, która posiada pole name oraz metodę move. Konstruktor klasy jest odpowiedzialny za inicjalizację pól obiektu, natomiast metoda move wykonuje operację przesunięcia o określoną odległość.

Dziedziczenie i polimorfizm

Kolejnym istotnym konceptem programowania obiektowego jest dziedziczenie. Pozwala ono na tworzenie hierarchii klas, gdzie klasy dziedziczące (podklasy) mogą korzystać z pól i metod swojej klasy bazowej (superklasy). Poniżej przedstawiono przykład wykorzystania dziedziczenia w TypeScript:

class Dog extends Animal {
  bark() {
    console.log('Woof! Woof!');
  }
}

const dog = new Dog('Burek');
dog.bark(); // Woof! Woof!
dog.move(10); // Burek moved 10m.

Klasa Dog dziedziczy po klasie Animal za pomocą słowa kluczowego extends. Dzięki temu może odwoływać się do pól i metod klasy bazowej oraz definiować własne zachowanie.

Interfejsy

Interfejsy stanowią kolejny ważny element programowania obiektowego w TypeScript. Za ich pomocą możliwe jest narzucanie określonych struktur i zachowań dla klas implementujących dany interfejs. Przykładowo:

interface Shape {
  area(): number;
}

class Circle implements Shape {
  radius: number;

  constructor(radius: number) {
    this.radius = radius;
  }

  area() {
    return Math.PI * this.radius ** 2;
  }
}

const circle = new Circle(5);
console.log(circle.area()); // ~78.54

Interfejs Shape narzuca implementującej go klasie posiadanie metody area zwracającej wartość liczbową. Klasa Circle spełnia ten warunek poprzez dostarczenie implementacji metody area dla koła.

Typy generyczne

Kolejną zaawansowaną techniką programowania obiektowego, którą warto poznać w kontekście TypeScript, są typy generyczne. Pozwalają one na tworzenie elastycznych i wielokrotnego użytku komponentów, które mogą operować na różnych typach danych. Przykładem może być generyczna klasa Map, która przechowuje pary klucz-wartość dla dowolnych typów.

class Map<K, V> {
  private map: { [key: string]: V } = {};

  set(key: K, value: V) {
    this.map[key.toString()] = value;
  }

  get(key: K): V {
    return this.map[key.toString()];
  }
}

const numberMap = new Map<number, string>();
numberMap.set(1, 'One');
console.log(numberMap.get(1)); // One

const stringMap = new Map<string, boolean>();
stringMap.set('isRaining', true);
console.log(stringMap.get('isRaining')); // true

Abstrakcyjne klasy oraz metody

W TypeScript możliwe jest wykorzystanie abstrakcyjnych klas oraz metod, które stanowią swoiste szablony do implementacji przez klasy dziedziczące. Abstrakcyjna klasa może zawierać konkretną implementację części metody oraz wymuszać implementację innych przez klasy pochodne. Przykładem może być abstrakcyjna klasa Shape z metodą abstrakcyjną area:

abstract class Shape {
  abstract area(): number;

  logArea() {
    console.log(`Area is ${this.area()}`);
  }
}

Dzięki temu możliwe jest korzystanie z metody abstrakcyjnej area w klasach dziedziczących oraz rozszerzenie jej o konkretne zachowanie dla różnych kształtów geometrycznych.

Zastosowanie właściwości i metod statycznych

W języku TypeScript istnieje również możliwość korzystania z właściwości i metod statycznych. Są one związane bezpośrednio z klasami, a nie z ich instancjami, co oznacza że są dostępne na poziomie samej klasy, a nie obiektu utworzonego na podstawie tej klasy. Przykład:

class MathHelper {
  static PI: number = 3.14;

  static calculateCircleArea(radius: number): number {
    return this.PI * radius ** 2;
  }
}

console.log(MathHelper.calculateCircleArea(5)); // ~78.5

Obsługa wyjątków

Kolejnym istotnym zagadnieniem w programowaniu obiektowym jest obsługa wyjątków. W TypeScript możliwe jest definiowanie własnych typów błędów oraz ich hierarchii za pomocą klas. Dzięki temu można precyzyjnie identyfikować i obsługiwać różnego rodzaju sytuacje wyjątkowe. Przykład:

class CustomError extends Error {
  constructor(message: string) {
    super(message);
    this.name = 'CustomError';
  }
}

function processUserData(userData: any) {
  if (!userData.username) {
    throw new CustomError('Username is required');
  }
}

Dostępność modułów

Programowanie obiektowe w TypeScript umożliwia wykorzystanie modułów do przechowywania i organizowania kodu. Moduły pozwalają na grupowanie logicznie powiązanych elementów aplikacji, co ułatwia zarządzanie dużymi projektami i pozwala uniknąć konfliktów w nazewnictwie zmiennych czy funkcji. Poniżej przedstawiono prosty przykład użycia modułu:

// mathOperations.ts
export function add(a: number, b: number): number {
  return a + b;
}

export function subtract(a: number, b: number): number {
  return a - b;
}

// main.ts
import { add, subtract } from './mathOperations';

console.log(add(5, 3)); // 8
console.log(subtract(5, 3)); // 2

Rozszerzanie funkcjonalności za pomocą mixinów

Mixiny są użytecznym narzędziem w programowaniu obiektowym, które pozwalają na rozszerzenie funkcjonalności klas poprzez dodanie nowych pól i metod. W TypeScript możliwe jest wykorzystanie dekoratorów do tworzenia mixinów, co umożliwia dynamiczne rozszerzanie istniejących klas o nowe zachowania. Poniżej przedstawiono przykład mixinu dodającego metodę log do klasy:

function Loggable(constructor: T) {
  return class extends constructor {
    log(message: string) {
      console.log(message);
    }
  };
}

class MyClass {
  constructor(private name: string) {}
}

const LoggableMyClass = Loggable(MyClass);

const instance = new LoggableMyClass('Example');
instance.log('This is a log message');

Refaktoryzacja kodu obiektowego

Refaktoryzacja to proces polegający na zmianie struktury i organizacji kodu bez zmiany jego zachowania zewnętrznego. W programowaniu obiektowym w TypeScript refaktoryzacja może polegać na ekstrakcji wspólnych fragmentów kodu do osobnych modułów lub klas bazowych, eliminacji nadmiarowych powtórzeń oraz poprawie czytelności i zrozumiałości kodu. Przyjrzyjmy się przykładowi refaktoryzacji polegającej na ekstrakcji części logiki do osobnej klasy:

class Order {
  private items: string[];

  constructor(items: string[]) {
    this.items = items;
  }

  getTotalPrice() {
    let totalPrice = 0;
    for (let item of this.items) {
      totalPrice += this.calculatePrice(item);
    }
    return totalPrice;
  }

  private calculatePrice(item: string): number {
    // logic to calculate price
    return Math.random() * 100;
  }
}

// Refaktoryzacja

class PriceCalculator {
  calculatePrice(item: string): number {
    // logic to calculate price
    return Math.random() * 100;
  }
}

class OrderRefactored {
  private items: string[];
  
  constructor(items: string[]) {
    this.items = items;
  }

  getTotalPrice() {
    const priceCalculator = new PriceCalculator();
    let totalPrice = 0;
    
    for (let item of this.items) {
      totalPrice += priceCalculator.calculatePrice(item);
    }

    return totalPrice;
  }
}

Zastosowanie wzorców projektowych w TypeScript

Wzorce projektowe są sprawdzonymi rozwiązaniami problemów występujących podczas programowania obiektowego. W TypeScript można wykorzystać wiele popularnych wzorców takich jak Singleton, Fabryka, Obserwator czy Strategia dla poprawy jakości i elastyczności kodu oraz ułatwienia jego utrzymania. Poniżej przedstawiono przykład użycia wzorca Fabryki do tworzenia instancji różnych typów figur geometrycznych:

interface ShapeFactory {
 createShape(): Shape; 
}

class CircleFactory implements ShapeFactory { 
 createShape(): Circle { 
   return new Circle(); 
 } 
} 

class RectangleFactory implements ShapeFactory { 
 createShape(): Rectangle { 
   return new Rectangle(); 
 } 
}

Testowanie jednostkowe obiektów w TypeScript

W programowaniu obiektowym ważnym elementem jest testowanie jednostkowe tworzonych klas i metod. W TypeScript do tego celu często wykorzystuje się narzędzia takie jak Jest, Mocha czy Jasmine. Dzięki nim możliwe jest pisanie testów sprawdzających poprawność działania poszczególnych elementów aplikacji. Przykład użycia Jest do testowania prostej klasy:

// math.test.ts
import { MathHelper } from './mathHelper';

test('adds 1 + 2 to equal 3', () => {
  expect(MathHelper.add(1, 2)).toBe(3);
});

test('subtracts 5 - 3 to equal 2', () => {
  expect(MathHelper.subtract(5, 3)).toBe(2);
});

Wykorzystanie dekoratorów w TypeScript

Dekoratory są zaawansowanym narzędziem, które umożliwiają dodawanie metadanych do klas oraz ich elementów. W TypeScript dekoratory mogą być wykorzystane do modyfikacji zachowania klas oraz funkcji poprzez dodawanie dodatkowej logiki lub zmiany ich implementacji.

Praca z interfejsami generycznymi w TypeScript

Interfejsy generyczne stanowią potężne narzędzie programowania obiektowego pozwalające na tworzenie uniwersalnych struktur danych i zachowań. Dzięki nim można tworzyć elastyczne oraz wielokrotnego użytku komponenty bez konkretyzowania typów danych z góry. Przykład użycia interfejsu generycznego:

interface List<T> {
    elements: T[];
    
    add(element: T): void;
    remove(element: T): void;
    get(index: number): T;
}

class NumberList implements List<number> {
    elements: number[] = [];
    
    add(element: number): void {
        this.elements.push(element);
    }
    
    remove(element: number): void {
        const index = this.elements.indexOf(element);
        if (index !== -1) {
            this.elements.splice(index, 1);
        }
    }
    
    get(index: number): number {
        return this.elements[index];
    }
}

Obsługa równoległych operacji asynchronicznych w TypeScript

Często podczas programowania obiektowego istnieje potrzeba obsługi równoległych operacji asynchronicznych, takich jak pobieranie danych z wielu źródeł lub jednoczesna manipulacja wieloma danymi. W TypeScript możliwe jest wykorzystanie mechanizmów takich jak Promise.all oraz async/await do zarządzania równoległymi operacjami asynchronicznymi:

async function getDataFromMultipleSources() {
   try {
      const data1 = await fetchData('https://api.source1.com');
      const data2 = await fetchData('https://api.source2.com');
      
      return [data1, data2];
   } catch (error) {
      console.error(error);
   }
}

getDataFromMultipleSources().then(data => console.log(data));

Obsługa różnych typów zmiennych w TypeScript

W języku TypeScript istnieje kilka podstawowych typów zmiennych, które są wykorzystywane do przechowywania danych oraz ich manipulacji. Typy takie jak string, number, boolean, array czy object pozwalają na precyzyjne określenie rodzaju wartości przechowywanej przez zmienną oraz umożliwiają kompilatorowi TypeScript dokładniejszą analizę kodu. Poniżej przedstawiono przykład wykorzystania różnych typów zmiennych:

let name: string = John;
let age: number = 30;
let isStudent: boolean = false;
let hobbies: string[] = ['reading', 'cooking', 'traveling'];
let personData: {name: string, age: number} = {name: John, age: 30};

Tworzenie i wykorzystanie deklaracji typów dla niestandardowych obiektów w TypeScript

Kiedy tworzymy aplikację z większą ilością klas i interfejsów mogą pojawić się potrzeby tworzenia własnych deklaracji typów składających się z wielu obiektów. W przypadku niestandardowych obiektów, możemy definiować własne interfejsy oraz wwłasne typy danych za pomocą słowa kluczowego type. Poniżej znajduje się przykład definiowania własnych interfejsów i typów danych:

interface User {
  name: string;
  age: number;
}

type Point = {
  x: number;
  y: number;
};

const user: User = { name: John, age: 30 };
const coordinates: Point = { x: 10, y: 20 };

Praca z modułami w TypeScript

Moduły stanowią ważny mechanizm organizacyjny w języku TypeScript, dzięki którym można oddzielić i uporządkować kod źródłowy aplikacji. Za pomocą modułów możemy grupować powiązane ze sobą definicje klas, funkcji oraz innych elementów aplikacji.

Wykorzystanie właściwości optional w interfejsach TypeScript

W przypadku definicji interfejsów w TypeScript można stosować właściwość optional poprzez oznaczenie jej znakiem zapytania (?). Właściwość optional oznacza że nie musi być ona obecna we wszystkich implementacjach danego interfejsu. Pozwala to na elastyczne definiowanie struktury obiektów bez konieczności posiadania wszystkich pól. Przykładowe wykorzystanie właściwości optional:

interface Car {
    name: string;
    model?: string; 
    year?: number;
}

const car1 : Car = { name:Ford}; //ok
const car2 : Car = { name:Audi, model:A3, year :2020}; //ok
const car3 : Car = { name:Toyota, year :2018}; //ok 

Typy warunkowe

Kolejnym istotnym obszarem, który warto poznać w kontekście TypeScript, są typy warunkowe. Pozwala on na tworzenie bardziej precyzyjnych definicji typów w zależności od spełnienia określonych warunków. Dzięki nim możliwe jest uzyskanie bardziej elastycznych i precyzyjnych typów zmiennych. Przykładowo:

type OrderStatus = 'new' | 'processing' | 'shipped' | 'delivered';

interface Order {
  status: OrderStatus;
  // inne pola
}

type ProcessingOrder = Order & { status: 'processing', estimatedShipDate: Date };
type ShippedOrder = Order & { status: 'shipped', trackingNumber: string };
// itd.

Wielokrotne dziedziczenie interfejsów

Jedną z zalet TypeScript jest możliwość implementowania wielokrotnego dziedziczenia interfejsów, czyli korzystania z tego samego elementu o różnych nazwach (rozwiązanie flexible name). Pozwala to na tworzenie mniej skomplikowanych kodów, które jednak równie dobrze spełniają swoje zadanie. Przedstawię przykład takiej sytuacji:

interface Shape {
    color: string;
}

interface Circle extends Shape {
    radius: number;
}

interface Square extends Shape {
    sideLength: number;
}

Używanie modyfikatorów dostępu w TypeScript

Modyfikatory dostępu pozwalają na kontrolowanie widoczności pól oraz metod zdefiniowanych w klasach. W TypeScript dostępne są trzy rodzaje modyfikatorów dostępu: public, private oraz protected. Kontrolują one dostęp do poszczególnych elementów klasy dla innych części kodu. Przykład użycia:

class Person {
    public name: string;   // można odczytać i zmienić wartość poza klasą Person
    private age: number;   // można odczytać i zmienić tylko wewnątrz klasy Person
    protected address: string;  // można odczytać i zmienić tylko w klasach dziedziczących lub tym samym module
}

Rozszerzenie generycznych klas abstrakcyjnych

Możliwe jest wykorzystanie generycznych klas abstrakcyjnych do stworzenia uniwersalnych szablonów, które mogą być używane do tworzenia różnorodnych obiektów bez konieczności powielania kodu. Takie rozwiązanie pozwala na elastyczne skalowanie aplikacji oraz ułatwia utrzymanie czystości i porządku w kodzie źródłowym. Poniżej znajduje się przykład wykorzystania generycznej klasy abstrakcyjnej:

abstract class Repository<T> {
  abstract save(item: T): void;
  abstract remove(id: number): void;
  abstract getById(id:number): T;
}

Podsumowanie

Programowanie obiektowe w języku TypeScript oferuje szeroki zakres możliwości oraz zaawansowanych technik, które pozwalają tworzyć skalowalne, elastyczne oraz efektywne aplikacje. Poznanie wszystkich wymienionych aspektów programowania obiektowego w TypeScript pozwoli na lepsze zrozumienie jego mechanizmów oraz umożliwi budowanie bardziej zaawansowanych projektów.

Przeczytaj o Programowanie w TypeScript także tutaj:
cyberlogic.pl 2024 - copyright © | nasz team | przydatne linki | site mapa | rss | polityka prywatności
Katalog-Blogow.pl katalog stron