Тверской государственный университет
Опубликован: 02.12.2009 | Доступ: свободный | Студентов: 3204 / 514 | Оценка: 4.41 / 4.23 | Длительность: 11:54:00
ISBN: 978-5-9963-0259-8
Лекция 5:

Процедуры и функции - методы класса

< Лекция 4 || Лекция 5: 12345 || Лекция 6 >

Что нужно знать о методах?

Знания формального синтаксиса и семантики недостаточно, чтобы эффективно работать с методами. Рассмотрим сейчас несколько важных вопросов, касающихся различных сторон работы с методами класса.

Почему у методов мало аргументов?

Методы класса имеют значительно меньше аргументов, чем процедуры и функции в классическом процедурном стиле программирования, когда не используется концепция классов. За счет чего происходит уменьшение числа аргументов у методов? Ведь аргументы играют важную роль - они передают методу информацию, нужную ему для работы, и возвращают информацию - результаты работы метода - программе, вызвавшей метод.

Все дело в том, что методы класса - это не просто набор процедур и функций, это методы, обслуживающие данные класса. Все поля класса доступны любому методу по определению. Нужно четко понимать, что в момент выполнения программной системы работа идет с объектами - экземплярами класса. Из полей соответствующего объекта - цели вызова - извлекается информация, нужная методу в процессе работы, а работа метода чаще всего сводится к обновлению значений полей этого объекта. Поэтому понятно, что методу не нужно через входные аргументы передавать информацию, содержащуюся в полях. Если в результате работы метода обновляется значение некоторого поля, то, опять-таки, не нужен никакой выходной аргумент.

Поля класса или функции без аргументов?

Поля хранят информацию о состоянии объектов класса. Состояние объекта динамически изменяется в ходе вычислений - обновляются значения полей. Часто возникающая дилемма при проектировании класса: что лучше - создать ли поле, хранящее информацию, или создать функцию без аргументов, вычисляющую значение этого поля всякий раз, когда это значение понадобится. Решение дилеммы - это вечный для программистов выбор между памятью и временем. Если предпочесть поле, то неизбежны дополнительные расходы памяти. Они могут быть значительными, когда создается большое число объектов, ведь поле должен иметь каждый объект. Если предпочесть функцию, то это потребует временных затрат на вычисление значения, они могут быть значительными, если функция вызывается многократно, а ее вычисление требует значительно больших затрат в сравнении с выбором текущего значения поля.

Если бы синтаксис описания метода допускал отсутствие скобок у функции (метода) в случае, когда список аргументов отсутствует, то клиент класса мог бы и не знать, обращается он к полю или к методу. Такой синтаксис принят, например, в языке Eiffel. Преимущество такого подхода в том, что изменение реализации никак не сказывается на клиентах класса. В языке C# это не так. Когда мы хотим получить длину строки, то пишем s.Length, точно зная, что Length - это поле, а не метод класса string. Если бы по каким-либо причинам разработчики класса string решили изменить реализацию и заменить поле Length соответствующей функцией, то ее вызов имел бы вид s.Length().

Пример: Две версии класса Account

Продемонстрируем рассмотренные выше вопросы на примере проектирования классов Account и Account1, описывающих такую абстракцию данных, как банковский счет. Определим на этих данных две основные операции - занесение денег на счет и снятие денег. В первом варианте - классе Account - будем активно использовать поля класса. Помимо двух основных полей - credit и debit, хранящих приход и расход счета, введем поле balance, задающее текущее состояние счета, и два поля, связанные с последней выполняемой операцией. Поле sum будет хранить сумму денег текущей операции, а поле result - результат выполнения операции. Увеличение числа полей класса приведет, как следствие, к уменьшению числа аргументов у методов класса. Вот описание нашего класса:

/// <summary>
  /// Класс Account определяет банковский счет.
  /// Простейший вариант с возможностью трех операций:
  /// положить деньги на счет, снять со счета, узнать баланс.
  /// Вариант с полями
  /// </summary>
  public class Account
  {
    //закрытые поля класса
    int debit=0, credit=0, balance =0;
    int sum =0, result=0;
    /// <summary>
    /// Зачисление на счет с проверкой
    /// </summary>
    /// <param name="sum">зачисляемая сумма</param>
    public void putMoney(int sum)
    {
      this.sum = sum;
      if (sum >0)
      {
        credit += sum; balance = credit - debit; result =1;
      }
      else result = -1;
      Mes();
    }//putMoney
    /// <summary>
    /// Снятие со счета с проверкой
    /// </summary>
    /// <param name="sum"> снимаемая сумма</param>
    public void getMoney(int sum)
    {
      this.sum = sum;
      if(sum <= balance)
      {
        debit += sum; balance = credit - debit; result =2;
      }
      else result = -2;
      Mes();
    }//getMoney
    /// <summary>
    /// Уведомление о выполнении операции
    /// </summary>
    void Mes()
    {
      switch (result)
      {
        case 1:
          Console.WriteLine("Операция зачисления денег прошла успешно!");
          Console.WriteLine("Cумма={0}, Ваш текущий баланс={1}",
            sum,balance);
          break;
        case 2:
          Console.WriteLine("Операция снятия денег прошла успешно!");
          Console.WriteLine("Cумма={0}, Ваш текущий баланс={1}",
            sum,balance);
          break;
        case -1:
          Console.WriteLine("Операция зачисления денег не выполнена!");
          Console.WriteLine("Сумма должна быть больше нуля!");
          Console.WriteLine("Cумма={0}, Ваш текущий баланс={1}",
            sum,balance);
          break;
        case -2:
          Console.WriteLine("Операция снятия денег не выполнена!");
          Console.WriteLine("Сумма должна быть не больше баланса!");
          Console.WriteLine("Cумма={0}, Ваш текущий баланс={1}",
            sum,balance);
          break;
        default:
          Console.WriteLine("Неизвестная операция!");
          break;
      }
    }
  }//Account

Как можно видеть, только у методов getMoney и putMoney имеется один входной аргумент. Это тот аргумент, который нужен по сути дела, поскольку только клиент может решить, какую сумму он хочет снять или положить на счет. Других аргументов у методов класса нет, вся информация передается через поля класса. Уменьшение числа аргументов приводит к повышению эффективности работы с методами, так как исчезают затраты на передачу фактических аргументов. Но за все надо платить. В данном случае усложняются сами операции работы со вкладом, поскольку нужно в момент выполнения операции обновлять значения многих полей класса. Закрытый метод Mes вызывается после выполнения каждой операции, сообщая о том, как прошла операция, и информируя клиента о текущем состоянии его баланса.

А теперь спроектируем аналогичный класс Account1, отличающийся только тем, что у него будет меньше полей. Вместо поля balance в классе появится соответствующая функция с этим же именем, вместо полей sum и result появятся аргументы у методов, обеспечивающие необходимую передачу информации. Вот как выглядит этот класс:

/// <summary>
  /// Класс Account1 определяет банковский счет.  
  /// Вариант с аргументами и функциями
  /// </summary>
  public class Account1
  {
    //закрытые поля класса
    int debit=0, credit=0;
    /// <summary>
    /// Зачисление на счет с проверкой
    /// </summary>
    /// <param name="sum">зачисляемая сумма</param>
    public void putMoney(int sum)
    {
      int res =1;
      if (sum >0)credit += sum;          
      else res = -1;
      Mes(res,sum);
    }//putMoney
    /// <summary>
    /// Снятие со счета с проверкой
    /// </summary>
    /// <param name="sum"> снимаемая сумма</param>
    public void getMoney(int sum)
    {
      int res=2;
      if(sum <= balance())debit += sum;          
      else res = -2;
      balance();
      Mes(res, sum);
    }//getMoney
  /// <summary>
  /// вычисление баланса
  /// </summary>
  /// <returns>текущий баланс</returns>
    int balance()
    {
      return(credit - debit);              
    }
    /// <summary>
    /// Уведомление о выполнении операции
    /// </summary>
    void Mes(int result, int sum)
    {
      switch (result)
      {
        case 1:
          Console.WriteLine("Операция зачисления денег прошла успешно!");
          Console.WriteLine("Cумма={0}, Ваш текущий баланс={1}",
            sum,balance());
          break;
        case 2:
          Console.WriteLine("Операция снятия денег прошла успешно!");
          Console.WriteLine("Cумма={0}, Ваш текущий баланс={1}",
            sum,balance());
          break;
        case -1:
          Console.WriteLine("Операция зачисления денег не выполнена!");
          Console.WriteLine("Сумма должна быть больше нуля!");
          Console.WriteLine("Cумма={0}, Ваш текущий баланс={1}",
            sum,balance());
          break;
        case -2:
          Console.WriteLine("Операция снятия денег не выполнена!");
          Console.WriteLine("Сумма должна быть не больше баланса!");
          Console.WriteLine("Cумма={0}, Ваш текущий баланс={1}",
            sum,balance());
          break;
        default:
          Console.WriteLine("Неизвестная операция!");
          break;
      }
    }
  }//Account1

Сравнивая этот класс с классом Account, можно видеть, что число полей сократилось с 5 до двух, упростились основные методы getMoney и putMoney. Но в качестве платы у класса появился дополнительный метод balance(), у метода Mes теперь появились два аргумента. Какой класс лучше? Однозначно сказать нельзя, все зависит от контекста, приоритетов, заданных при создании конкретной системы.

Приведу процедуру класса Testing, тестирующую работу с классами Account и Account1:

public void TestAccounts()
    {
      Account myAccount = new Account();
      myAccount.putMoney(6000);
      myAccount.getMoney(2500);
      myAccount.putMoney(1000);
      myAccount.getMoney(4000);
      myAccount.getMoney(1000);
      //Аналогичная работа с классом Account1
      Console.WriteLine("Новый класс и новый счет!");
      Account1 myAccount1 = new Account1();
      myAccount1.putMoney(6000);
      myAccount1.getMoney(2500);
      myAccount1.putMoney(1000);
      myAccount1.getMoney(4000);
      myAccount1.getMoney(1000);
    }

На рис. 5.1 показаны результаты работы этой процедуры.

Тестирование классов Account и Account1

Рис. 5.1. Тестирование классов Account и Account1
< Лекция 4 || Лекция 5: 12345 || Лекция 6 >
Дарья Федотова
Дарья Федотова
Надежда Савина
Надежда Савина

Записалась на платный курс Программирование на С# http://www.intuit.ru/studies/professional_retraining/951/video_courses/356/info. Оплату пока не производила.

Могу ли я скачать/прочитать текст прослушиваемых лекций? Особенно интересуют задания

Гостибой Файзов
Гостибой Файзов
Россия
Владимир Романов
Владимир Романов
Россия, Москва, МПУ им Н.К. Крупской