Садржај

Објектно–оријентисана парадигма

Објектно–оријентисану парадигму сте веома детаљно изучили у трећем разреду у предмету Објектно-оријентисано програмирање, па се нећемо пуно задржавати на њеном описивању.

Објектно–оријентисана парадигма (ООП) је модел програмирања који се темељи на организовању и структуирању кода око „објеката”, који су инстанциране представе стварних ентитета (на пример, аутомобил, штампач, запослени) или апстрактних концепта (на пример, датотека, израз). ООП промовише начин размишљања о програмима као колекцији објеката који међусобно комуницирају путем дефинисаних интеракција. Објекти једни другима шаљу поруке (тако што позивају методе које су део јавног интерфејса). Ова парадигма пружа низ предности као што су модуларност, поновно коришћење кода, лакше одржавање и разумевање, те олакшава развој комплексних система. ООП омогућава организацију кода на начин који одражава стварни свет и олакшава развој скалабилних и одрживих апликација. Објектно оријентисана парадигма је веома брзо прихваћена у софтверској индустрији и може се рећи да је данас вероватно и доминантна парадигма. ООП се данас примењује у многим програмским језицима као што су Java, Python, C++, C#, Ruby итд.

Слично као и струтурно програмирање и објектно-оријентисано програмирање је настало као одговор на софтверску кризу. Разбијањем монолитних програма у независне целине (класе и објекте), омогућено је да више програмера и тимова програмера подели посао тако да софтверске компоненте које независно програмирају комуницирају само договореног јавног интерфејса. На тај начин један програмер не мора (а коришћењем концепта енкапсулације и не може) да зна на који начин је имплементирана компонента коју други програмер програмира. То даје слободу другом програмеру да у било ком тренутку замени унутрашњу имплементацију (на пример, употреби неки ефикаснији алгоритам или структуре података), а да други програмери због тога не морају да мењају свој кôд. Додатно, једном испрограмиране класе се могу користити у већем броју различитих програма (на пример, класа за представљање времена и датума, класа за представљање листа тј. динамички проширивих низова или класа за представљање дугмета на корисничком интерфејсу). Механизам наслеђивања даје могућност да се класе прошире и на тај начин прилагоде новим, сложенијим захтевима. На пример, класа којом се представљају електрични аутомобили може да наследи класу којом се предсављају аутомобили, додајући податке о батерији. Полиморфизам омогућава да се се програмирање неких компонената врши само према апстрактном интерфејсу других поткомпонената, а да се те поткомпоненте накнадно замене новим конкретним имплементацијама. Тако, на пример, програм за обраду текста може да нуди функционалност штампања, обраћајући се апстрактној класи тј. интерфејсу који нуди заједничке функционалности за све штампаче. Оваквом систему у било ком тренутку може бити додата класа којом се представља неки нови, конкретни штампач, док год та класа имплементира све функционалности тог заједничког интерфејса.

Подсетимо се још једном кључних концепата ООП, које сте проучавали у трећем разреду:

  1. Објекти: Објекти су инстанце класа које дефинишу својства (атрибуте) и понашање (методе). На пример, у апликацији за управљање корисницима, сваки корисник може бити представљен као објекат са својим атрибутима као што су име, мејл адреса и старост.

  2. Класе: Класе су шаблони који дефинишу структуру и понашање објеката. Оне садрже дефиниције атрибута и метода које ће објекти имати. На пример, „Корисник” може бити класа која дефинише својства и методе заједничке свим корисницима.

  3. Енкапсулација се односи на паковање података (атрибута) и функционалности (метода) унутар једног ентитета - објекта. Идеја је да се објекти делују као „капсуле” које садрже своје унутрашње стање и операције, док су детаљи имплементације скривени од спољашњег света тј. од програмера који користе класе и објекте. Енкапсулација омогућава да се приступ и баратање подацима врше само путем добро дефинисаних метода, чиме се осигурава конзистентно стање објекта и смањује ризик од нежељених модификација.

    Предности енкапсулације укључују:

    Скривање детаља имплементације: Енкапсулација омогућава да се промене у унутрашњој имплементацији објекта изврше без утицаја на кориснике тог објекта. Корисници само користе јавне методе за интеракцију са објектима, док је комплексност детаља сакривена.

    Заштита и валидација: Објекти могу примењивати правила за приступ и манипулацију подацима. Ово омогућава да се валидира унос података и да се осигура њихова исправност пре него што буду записани.

    Модуларност и поновно коришћење: Енкапсулација омогућава развијање модула који се могу користити независно од других делова апликације. Објекти се могу поновно користити у различитим контекстима, смањујући потребу за писањем истог кода више пута.

    Одржавање и рефакторисање: Промене у имплементацији објекта могу се обавити унутар саме капсуле без утицаја на остатак система. Ово олакшава одржавање и рефакторисање кода.

    Смањење грешака: Енкапсулација смањује могућност грешака јер се манипулација подацима обавља само путем строго дефинисаних метода које су тестиране и верификоване.

    Да би се постигла енкапсулација, програмски језици омогућавају дефинисање приступних модификатора који контролишу видљивост атрибута и метода изван објекта. Приступни модификатори омогућавају програмерима да прецизно дефинишу који делови објекта су доступни изван њега, чиме се имплементира принцип енкапсулације.

Подсетимо се ових концепата кроз следећи једноставан пример.

using System;

public class Circle
{
    // privatno polje u kom čuvamo poluprečnik
    private double radius;

    // konstruktor
    public Circle(double radius)
    {
        this.setRadius(radius);
    }

    // Metod za čitanje poluprečnika
    public double GetRadius()
    {
        return radius;
    }

    // Metod za promenu poluprečnika
    public void SetRadius(double newRadius)
    {
        // obezbeđujemo da poluprečnik ne bude negativan
        if (radius >= 0)
            this.radius = radius;
        else
            throw new ArgumentException("Poluprečnik ne može biti negativan.");
    }

    // Metod za izračunavanje površine
    public double CalculateArea()
    {
        return radius * radius * Math.PI;
    }

    // Metod za izračunavanje obima
    public double CalculateCircumference()
    {
        return 2 * radius * Math.PI;
    }
}

class Program
{
    static void Main()
    {
        // Kreiramo objekat, instancu klase krug
        Circle circle = new Circle(5.0);

        // Menjamo mu poluprečnik
        circle.SetRadius(7.0);

        // Računamo i ispisujemo poluprečnik, površinu i obim
        Console.WriteLine($"Radius: {circle.GetRadius()}");
        Console.WriteLine($"Area: {circle.CalculateArea()}");
        Console.WriteLine($"Circumference: {circle.CalculateCircumference()}");
    }
}
  1. Наслеђивање: Наслеђивање омогућава креирање нових класа на основу већ постојећих класа. Ова парадигма подржава идеју да нове класе могу наследити (преузети) интерфејс тј. својства и методе постојећих класа, чиме се олакшава поновно коришћење кода и организација структуре.

    Базна Класа: Базна класа је оригинална класа која дефинише својства и методе које ће бити наслеђене. Ова класа се такође назива и родитељском класом, надкласом или суперкласом.

    Изведена Класа: Изведена класа је нова класа која се креира на основу постојеће класе. Ова класа наслеђује својства и методе базне класе, али може додати своје додатне карактеристике или предефинисати постојеће методе.

    Наслеђивање атрибута и метода: Изведена класа аутоматски наслеђује све атрибуте (својства) и методе (укључујући и њихову имплементацију) дефинисане у базној класи. То омогућава да се сличне класе репрезентују на логичан и организован начин.

    Додатни атрибути и методи: Изведена класа може додати нове методе и атрибути који нису присутни у базној класи. Ово омогућава да се прошири функционалност и додају нове карактеристике без утицаја на базну класу.

    Предефинисање Метода: Изведена класа може предефинисати (енгл. override) методе базне класе тако да се прилагоде специфичним потребама. Ово омогућава да се исте методе понашају другачије у различитим контекстима.

    Вишеструко наслеђивање (у одређеним језицима): Неки програмски језици подржавају вишеструко наслеђивање, што значи да изведена класа може наследити својства и методе из више базних класа. Међутим, ова функционалност може постати комплексна и довести до „дијамантског проблема” у неким случајевима.

    Наслеђивање омогућава ефикасно поновно коришћење кода, јер се постојеће имплементације могу користити као основа за нове класе. Ово олакшава организацију, одржавање и разумевање кода, јер се сличне класе групишу заједно, а заједнички код се дели.

  2. Полиморфизам: Полиморфизам омогућава истоименим методама да се понашају различито у зависности од класе којој припадају тј. омогућава различитим објектима да се понашају на различите начине, чак и ако деле исти назив методе. Полиморфизам омогућава да се иста метода користи за различите типове објеката, чиме се поједностављује кôд и олакшава рад са разноликим ентитетима.

    Полиморфизам омогућава и да класа има методе са истим именом, али са различитим потписима.

    Предефинисање метода: Када изведена класа наслеђује методу од своје базне класе, може предефинисати (override) ту методу да би се прилагодила свом специфичном понашању. Ово омогућава да метода има другачији садржај у различитим класама.

    Динамичко везивање: Полиморфизам омогућава да се одабере права верзија методе током извршавања, а не током компилације. Ово се назива динамичко повезивање или касно повезивање, и омогућава да се изабере права метода на основу стварног типа објекта који се обрађује.

    Интерфејси и апстрактне класе: Полиморфизам често се постиже кроз употребу интерфејса и апстрактних класа. Интерфејси дефинишу скуп метода које се морају имплементирати у изведеним класама, док апстрактне класе садрже недефинисане методе које се морају имплементирати у конкретним подкласама.

    Поједностављена комуникација: Полиморфизам омогућава да се исте операције примењују на различите типове објеката без потребе за анализом њиховог типа наредбама гранања. На пример, ако имате различите геометријске облике, можете користити исту методу за израчунавање површине без обзира на то који облик користите.

    Полиморфизам чини код флексибилнијим, омогућавајући програмерима да раде са различитим типовима објеката користећи исте методе и интерфејс.

Подсетимо се наслеђивања и полиморфизма тако што ћемо увести апстрактну класу за облике.

using System;

// Apstraktna bazna klasa za predstavljanje bilo kog oblika
public abstract class Shape
{
    // apstraktna metoda za izračunavanje površine
    // (potrebno je da se implementira u svim izvedenim klasama)
    public abstract double CalculateArea();
}

// Izvedena klasa za predstavljanje krugova
// Krug je vrsta oblika, pa klasa Circle nasleđuje klasu Shape
public class Circle : Shape
{
    // poluprečnik
    private double radius;

    // Konstruktor
    public Circle(double radius)
    {
        this.radius = radius;
    }

    // Implementacija izračunavanja površine
    public override double CalculateArea()
    {
        return Math.PI * radius * radius;
    }
}

// Izvedena klasa za predstavljanje pravougaonika
// Pravougaonik je vrsta oblika, pa klasa Rectangle nasleđuje klasu Shape
public class Rectangle : Shape
{
    // Dužina i širina
    private double width;
    private double height;

    // Konstruktor
    public Rectangle(double width, double height)
    {
        this.width = width;
        this.height = height;
    }

    // Implementacija izračunavanja površine
    public override double CalculateArea()
    {
        return width * height;
    }
}

class Program
{
   static void Main()
   {
       // Niz oblika
       Shape[] shapes = new Shape[]
       {
           new Circle(5.0),
           new Rectangle(4.0, 6.0),
           new Circle(3.5),
           new Rectangle(2.0, 8.0)
       };

       // Izračunavanje ukupne površine
       double totalArea = 0.0;
       foreach (Shape shape in shapes)
           totalArea += shape.CalculateArea();

       // Ispis ukupne površine
       Console.WriteLine($"Total Area of Shapes: {totalArea}");
   }
}

Упоредимо овај програм са еквивалентним програмом написаном у духу императивне парадигме.

class Program {
    struct Circle {
        public double radius;
    }

    static double CircleArea(Circle circle) {
        return circle.radius * circle.radius * Math.PI;
    }

    struct Rectangle {
        public double width, height;
    }

    static double RectangleArea(Rectangle rectangle) {
        return rectangle.width * rectangle.height;
    }

    static void Main() {
        Circle circle1; circle1.radius = 5.0;
        Circle circle2; circle2.radius = 3.5;
        Rectangle rectangle1; rectangle1.width = 4.0; rectangle1.height = 6.0;
        Rectangle rectangle2; rectangle2.width = 2.0; rectangle2.height = 8.0;

       // Izračunavanje ukupne površine
       double totalArea = 0.0;
       totalArea += CircleArea(circle1);
       totalArea += CircleArea(circle2);
       totalArea += Rectangle1(rectangle1);
       totalArea += Rectangle2(rectangle2);

       // Ispis ukupne površine
       Console.WriteLine($"Total Area of Shapes: {totalArea}");
    }
}

Примећујемо да су структуре за представљање круга и правоугаоника (Circle и Rectangle) потпуно неповезане и да стога нисмо могли направити низ облика. Могли бисмо увести наткласу за облик и тако омогућити креирање низа облика, али наредно решење, као и претходно, не користи полиморфизам и не може се сматрати написаним у духу ООП.

class Program {
    class Shape { }

    class Circle : Shape {
        private double radius;

        public Circle(double radius) {
            this.radius = radius;
        }

        public double CircleArea() {
            return radius * radius * Math.PI;
        }
    }

    class Rectangle : Shape {
        private double width, height;

        public Rectangle(width, height) {
            this.width = width;
            this.height = height;
        }

        public double RectangleArea(Rectangle rectangle) {
            return width * height;
        }
   }


    static void Main() {
       // Niz oblika
       Shape[] shapes = new Shape[]
       {
           new Circle(5.0),
           new Rectangle(4.0, 6.0),
           new Circle(3.5),
           new Rectangle(2.0, 8.0)
       };

       // Izračunavanje ukupne površine
       double totalArea = 0.0;
       foreach (Shape shape in shapes)
           if (shape is Circle)
              totalArea += shape.CircleArea();
           else if (shape is Rectangle)
              totalArea += shape.RectangleArea();

       // Ispis ukupne površine
       Console.WriteLine($"Total Area of Shapes: {totalArea}");
    }
}

Гранање на основу типа је веома проблематично, јер ствара проблеме приликом проширивања система новим класама. Ако бисмо, на пример, додали класу за представљање троуглова, у правом ООП решењу заснованом на полиморфизму не би било потребе да се мења код који израчунава укупну површину. Са друге стране, у претходном решењу би у том коду било потребно додати још једну грану којом се обрађују троуглови. Иако то није проблем у овако кратком програму, у дужим програмима није тешко замислити да постоје десетине па и стотине места где се обрађују низови облика, па је измена сваког од њих након додавања нове класе веома напоран посао, подложан грешкама (компилатор нас неће упозорити ако случајно на неком месту заборавимо да додамо грану у којој се обрађује новододата класа).

(Created using Swinx, RunestoneComponents and PetljaDoc)
© 2022 Petlja
A- A+