Модул 3. Увод в обектно-ориентирано програмиране

Материали | Задачи | Решения

Съдържание

1. Дефиниране на класове

Абстрактни типове данни

  • Абстрактните типове данни описват: множество от данни и възможните операции в рамките на този тип.

  • Абстрактните типове данни ни позволяват да опишем конкретна структура, без обаче да се интересуваме от детайлите в тази реализация.

Дефиниране на прост клас

  • Класовете служат за създаване на имплементация на aбстрактни типове данни

  • Класовете ни дават начин да опишем и създадем обекти

class Dice 
{

}

Именуване на класове

  • Класовете са PascalCase

  • Използвайте описателни съществителни

  • Избягвайте абревиатури

class Dice { … }
class BankAccount { … }
class IntegerCalculator { … }

Членове на класа

  • Класа съдържа състояния и действия

  • Полетата съдържат състоянието

  • Методите описват действието

class Dice {
  int sides;
  string type;
  void Roll() { … }
}

Създаване на обект

Класът може да има много инстанции (обекти)

class Program 
{
  public static void Main() 
  {
    Dice diceD6 = new Dice();
    Dice diceD8 = new Dice();
  }
}

Обектна референция

Декларирането на променлива създава референция

Dice diceD6 = new Dice();

Класове и Обекти

Класовете ни позволяват да описваме и създаваме обекти. Обектът е една инстанция на класа.

Задача: Дефинирайте клас Person

Дефинирайте клас Person, като за него пазете информация за името и възрастта на човек и реализирайте единствено действието IntroduceYourself(), което отпечатва представяне на човека. След това създайте и използвайте обект от класа Person.

class Person {
  private string name;
  private int age;
  public String Name { //реализираме свойство Name
    get { return name; }
    set { name = value; }
  }
  public int Age { //реализираме свойство Age
    get { return age; }
    set { age = value; }
  }
  public void IntroduceYourself() {
    Console.WriteLine("Здравейте! Аз съм {0} и съм на {1} години.", name, age);
  }
}

Сега е време да използваме класа и да направим обект в Main метода ни в Program.cs:

static void Main(string[] args) 
{
  Person firstPerson = new Person();
  firstPerson.Name = "Гошо";
  firstPerson.Age = 15;
  firstPerson.IntroduceYourself();
}

За момента няма да акцентираме на теоретичния смисъл на кода от по-рано – той ще се уточни допълнително в следващите теми.

Задача: Човекът и неговите пари

Дефинирайте клас Person, като за него пазете информация за името и възрастта на човек, както и информация за банкови сметки (клас BankAccount). Направете метод GetBalance(), който дава информация каква е общата стойност на парите, които притежава човек.

  • Първо ще създадем файл за клас BankAccount.

  • След това ще направим възможно в Person да се пази списък от банковите сметки на човека

  • Последно в класа Person ще добавим метод, който изчислява сумата от балансите на всички смекти в списъка

class BankAccount 
{
  private int id;
  private double balance;

  public int ID 
  {
    get { return id; }
    set { id = value; }
  }

  public double Balance 
  {
    get { return balance; }
    set { balance = value; }
  }
}

В Person.cs добавете поле, в което ще се пази списък от банковите сметки, както и свойство:

class Person 
{
  // TODO: добавете полета за име и възраст
  private List<BankAccount> accounts = new List<BankAccount>();
  // TODO: добавете свойства за име и възраст
  public List<BankAccount> Accounts 
  {
    get { return accounts; }
    set { accounts = value; }
  }
}

В Person.cs добавете метода, който ще изчислява стойността на сумата от балансите на всички сметки:

class Person 
{
  // ...
  public double GetBalance() 
  {
    return accounts.Sum(element => element.Balance);
  }
}
  • Създайте три обекта от клас Person в Main().

  • Създайте един обект от клас BankAccount, задайте му ID и баланс и го добавете към първия човек, използвайки метода Add() - не забравяйте, че .Accounts е списък и има метод Add()

  • Аналогично за втория човек създайте две сметки и му ги добавете, а за третия – три;

  • Изкарайте трите суми – състоянието на първия, втория и третия човек

2. Полета и методи

Елементи на класа

  • Клас се дефинира чрез състояние и поведение

  • Полетата съхраняват състоянието

  • Методите описват поведението

class Dice 
{
  int sides;
  string type;
  void Roll(){ … }
}

Полета

Полетата на класа имат тип и име

class Dice 
{
  string type;
  int sides;
  int[] rollFrequency;
  Person owner;

}

Модификатори

  • Класовете и елементите на класа имат модификатори

  • Модификаторите определят видимостта

public class Dice 
{
  private int sides;
  public void Roll(int amount);
}

Свойства

Използва се за създаване на методи за четене (getters) и методи за промяна (setters).

class Dice 
{
  private int sides;
  public int Sides
  {
    public get { return this.sides; }
    public set { this.sides = value; }
  }
}

Задача: Описване на клас Банкова сметка

Създайте клас BankAccount със следното съдържание:

private string id;
private decimal balance;
public string Id
{
      get { return this.id; }
      set { this.id = value; }
}
public decimal Balance
{
      get { return this.balance; }
      set { this.balance = value; }
}

Методи

Те са изпълним код (алгоритъм), който променя състоянието:

class Dice 
{
  public int sides;
  private Random rnd = new Random();
  public int Roll()
  {
     int rollResult = rnd.Next(1, this.sides + 1); 
     return rollResult;
  }
}

Задача: Getter-и и Setter-и

Създайте клас BankAccount със следното съдържание:

private double balance;
public void Deposit(double amount)
{
  this.balance += amount;
}
public void Withdraw(double amount)
{
  this.balance -= amount; 
}
public override string ToString()
{ 
  return $"Account {this.id}, balance {this.balance}";
}

Конструктори

Специален вид методи, извиквани при създаване на обекта

class Dice
{
  int sides;
  public Dice()
  {
    this.sides = 6;
  }
}

Може да имате множество конструктори за даден клас

class Dice
{
  int sides;
  public Dice()
  {
    this.sides = 6;
  }
  public Dice(int sides)
  {
    this.sides = sides;
  }
}

Начално състояние на обекта

Конструкторите задават началното състояние на обекта

class Dice
{
  int sides; int[] rollFrequency;
  public Dice(int sides)
  {
    this.sides = sides;
    this.rollFrequency = new int[sides];
  }
}

Верижно извикване на конструктори

Конструкторите могат да се извикват един друг

class Dice 
{
  int sides;
  public Dice() : this(6)
  {
  }
  public Dice(int sides) 
  {
    this.sides = sides;
  }
}

Задача: Дефиниране на клас физическо лице

Създайте клас Person със следното съдържание

public class Person
{
  private string name;
  private int age;
  private List<BankAccount> accounts;
  public Person(string name, int age)
      : this(name, age, new List<BankAccount>))
    {}
  public Person(string name, int age, List<BankAccount> accounts)
  {
    this.name = name;
    this.age = age;
    this.accounts = accounts;
  }
}

3. Енкапсулация на данни

Капсулация

Процесът на обединяване на кода и данните в едно цяло (обект).

Полетата на обекта трябва да са private

private int age; 

Използване на getters и setters за достъп до данните

class Person
{
  public int Age => return this.age
}  

Задача: Клас Creature

  • Създайте клас Creature

  • Creature трябва да има полета name, years, areal, съответно за име, възраст и местообитание

  • Създайте методи за достъп до обектите getters и setters за полетата

private string name;
private int years;
Private string areal;

public string getName()
{
  return this.name;
}
public void setName(string value)
{
  this.name = value;
}
public int getYears()
{
  return this.years;
}
public void setYears(int value)
{
  this.years = value;
}
public string getAreal()
{
  return this.areal;
}
public void setAreal(string value)
{
  this.areal = value;
}

Ключова дума this

  • this е препратка към текущия обект

  • this може да сочи към променлива, която е инстанция (представител) на текущия клас

public Person(string name)
{
  this.name = name;
}
  • this може да се предава като аргумент в метод или като извикване на конструктор

  • this може да се върне като стойност на метод

  • this може да извика метод на текущия клас

private string FirstName
{ 
  get { return this.fname } 
}
public string FullName
{
  return this.FirstName + " " + this.LastName
}
  • this може да извиква конструктор на текущия клас

public Person(string firstName, string lastName)
{
  this.firstName = firstName;
  this.lastName = lastName;
}
public Person (string fname, string lName, int age) : this(fName, lName);
{
  this.age = age;
}

Private Модификатор за достъп

Основен начин за капсулиране на обект и скриване на данни от външния свят

private string name;
Person (string name)
{
  this.name = name;
}
  • Класовете и интерфейсите не могат да са private. Идеята за интерфейс е да се даде възможност за връзка с "външния свят" – т.е. – трябва да са достъпни

  • Могат да бъдат достъпни само в декларацията на класа

Protected Модификатор за достъп

  • Могат да бъдат достъпни само от подкласове

class Person
{
  protected string FullName { get; set; }
}
  • Модификаторът за достъп protected не може да бъде приложен за класове и интерфейси

  • Предотвратява външни класове да се опитват да го използват

Internal модификатор за достъп

internal е модификатор по подразбиране в C#.

class Person
{
  string Name { get; set; }
  internal int Age { get; set; }
}

Дава достъп на всеки друг клас в същия проект

Team rm = new Team("Real");
rm.Name("Real Madrid");

Public модификатор за достъп

Клас, метод, конструктор, деклариран в public клас може да бъде достъпен от всеки клас, принадлежащ на .NET

public class Person
{
  public string Name { get; set; }
  public int Age { get; set; }
}
  • Употребата се налага ако се опитваме да достъпим public клас в друг namespace

  • Методът Main() в приложението трябва да е public

  • Интерфейсите са public. Тъй като смисълът им е да дават връзка с външния свят

Задача: Подредете Persons по Name и Age

Create a class Person

public class Person {
  private string firstName;
  private string lastName;
  private int age;
  public string FirstName => return this.firstName;
  public int Age => return this.lastName;
  public override string ToString()
  {
    // TODO: Add logic
  }
}

Задача: Увеличение на заплатата

  • Добавете към класа Person поле salary

  • Добавете getter за заплата

  • Добавете метод, който променя заплатата с даден процент

  • Persons, по-млади от 30 вземат половината от увеличението

private double salary;
public void IncreaseSalary(double percent)
{
  if (this.age > 30)
    this.salary += this.salary * percent / 100;
  else
    this.salary += this.salary * percent / 200;
}

Валидация

Валидацията на данни се случва в setters

public double Salary
{
  set
  {
    if (salary < 460)
      throw new ArgumentException("...");
    this.salary = value;
  }
}

Сътрудник на вашия клас трябва да се грижи за обработка на изключенията. Конструкторите използват private setter с валидационна логика.

public Person(string firstName, string lastName, int age, double salary)
{
  this.FirstName = firstName;
  this.LastName = lastName;
  this.Age = age;
  this.Salary = salary;
}

Гарантират валидно състояние на обекта при неговото създаване.

Задача: Валидация на данни

  • Разширете Person с валиация за всяко поле

  • Names трябва да са с не по-малко от 3 символа

  • Age не може да е нула или отрицателно

  • Salary да не е по-малко от 460

// TODO: Add validation for firstName
// TODO: Add validation for lastName
private void setAge(int age)
{
  if (age < 1) 
    throw new ArgumentException("...");
  this.age = age;
}
// TODO: Add validation for salary

Непроменими (Immutable) обекти

Когато имате препратка reference към инстанция на обект, съдържанието, на която не може да бъде променяно.

string myString = "old String"
Console.WriteLine( myString );
myString.replaceAll( "old", "new" );
Console.WriteLine( myString );

Променими полета

Променимите private полета все още не са капсулирани

class Team 
{
  private String name;
  private List<Person> players;
  public List<Person> Players
  {
    get { return this.players; }
  }
} 

Тогава getter-а е също и setter

Задача: Първи и Резервен отбор

  • Разширете вашия проект с клас Team

  • Team трябва да има два комплекта отбори първи отбор и втори отбор

  • Въведете persons от клавиатурата и ги добавете към отбора

  • Ако те са по-млади от 40, тогава ги добавете към първи отбор

  • Изведете броя на играчите на всеки отбор

private string name;
private List<Person> firstTeam;
private List<Person> reserveTeam;
public Team(string name)
{
  this.name = name;
  this.firstTeam = new List<Person>();
  this.reserveTeam = new List<Person>();
}
public IReadOnlyCollection<Person> FirstTeam
{
 get { return this.firstTeam.AsReadOnly(); }
}
// TODO: add getter for reserve team
public void AddPlayer(Person player)
{
  if (player.Age < 40)
    firstTeam.Add(player);
  else
    reserveTeam.Add(player);
}

4. Статични полета и методи

Статични полета

Статичните полета в класа:

  • Принадлежат на самия клас

  • Имат една и съща стойност за всеки обект

  • Могат да бъдат достъпени и само чрез класа - без създаване на обект от този клас

Подобно на полетата останалите членове на класа също могат да бъдат статични.

Статични свойства

Статичните свойства в класа:

  • Принадлежат на самия клас

  • Могат да бъдат достъпени и само чрез класа - без създаване на обект от този клас

Използването на свойства е удобно, когато имаме статични полета, но не искаме да позволим тяхната промяна в друг клас, който използва нашия. Ако трябва да използваме само поле, то за да го достъпим извън класа, трябва да е public, което пък би позволило неговото изменение. Именно тук идват статичните свойства.

Задача: Преброй хората

Напишете програма, която да поддържа информация колко обекта от клас Person има създадени до момента. Реализирайте я използвайки статично поле и свойство.

Ще създадем статично поле, което ще поддържа информацията. След това ще направим статично свойство, което ще има само get, понеже в противен случай ще можем да манипулираме брояча, когато използваме класа, а в случая идеята е ползвателя на класа да не може да промени полето, в което е записан броя, а само да го достъпи.

Броячът ще се увеличава в рамките на конструктора на класа.

class Person 
{ // останалите части на класа са пропуснати
  private static int count = 0;
  public Person(string name, int age) 
  {
    // в конструктора добавяме реда, който променя стойността на count:
    Person.count += 1;
  }
  public static int Count 
  { // статично свойство
    get { return count; }
  }
}

Статични методи

Статичните методи в класа:

  • Принадлежат на самия клас

  • Могат да бъдат достъпени само чрез класа - без създаване на обект от този клас

  • Удобни са за извършване на действия върху всички обекти от класа или за извършване на действия, които нямат пряко отношение към обектите

Задача: Аритметични действия

Създайте клас, който поддържа статични методи за аритметични действия върху две цели числа:

  • Add(int, int) – събира числата

  • Multiply(int, int) – умножава числата.

Използвайте методите от този клас в Main метода да извършите засичане на команда и извършете операцията.

class Arithmetics
{
  public static int Add(int a, int b)
  {
    return a+b;
  }
  public static int Multiply(int a, int b) 
  {
    return a * b;
  }
}

Извиквайте методите по аналогичен начин в Main(): Arithmetics.Add(10, 15);

Статични класове

В решението оставихме класа си нестатичен. Това означава, че от него може да се направи обект. В случая това обаче би било безсмислено. За да не се допуска създаване на обект от даден клас, който има само статични членове ние можем да поставим думата static пред class: static class.

Когато отбележим един клас като статичен това означава, че неговите членове също ще са статични и от този клас няма да може да се създават обекти, а ще може членовете му да се ползват само статично. Много класове от .NET са статични (например Math)

Статични конструктори

Конструкторите в един клас също могат да бъдат статични.

Ако един конструктор е статичен той се изпълнява, когато едно от тези събития се случи за първи път:

  • Създаде се обект от класа (ако той е нестатичен)

  • Достъпва се статичен член от класа

Най-често статични конструктори се използват за инициализация на статични полета.

Задача: Заявка за корен

Напишете клас, който съдържа метод, който връща корен квадратен при подадена заявка. Възможно е да получите голям брой заявки, така че трябва да отговаряте бързо на всяка една от тях.

public static class SquareRootPrecalculator 
{
  public const int MaxValue = 1000;
  private static double[] sqrtValues;
  static SquareRootPrecalculator() 
  {
    sqrtValues = new double[MaxValue+1];
    for (int i = 1; i <= MaxValue; i++)
      sqrtValues[i] = Math.Sqrt(i);
  }
  public static double GetSqrt(int value) 
  {
    return sqrtValues[value];
  }
}

Last updated

Was this helpful?