Архив метки: контроль доступа

Главная / контроль доступа
1 Пост

Классы

Для начала мы поговорим о классах. Мы рассмотрим:

  • Классы
  • Как создаются объекты
  • Как получить доступ к данным в них

Обзор объектно-ориентированного программирования

  • Определите вещи, с которыми ваша программа имеет дело
  • Этими вещами (их «чертежами») являются классы
    • Классы содержат методы (поведение)
  • Объекты являются экземплярами этих вещей
  • Объекты содержат переменные экземпляров (состояние)

Переменные экземпляров

В Ruby, переменные экземпляров начинаются на @. К примеру, @name.

Интересно то, что в Ruby переменные экземпляра не объявляются. Они как бы просто оживают, когда вызваны впервые.

Они доступны для всех методов экземпляра класса.

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

Способ создания объектов в Ruby — вызов метода new в классе, фактически это вызывает метод initialize.

Метод initialize в Ruby — это специальный метод, который действует как конструктор, как мы увидим в примере ниже.

Состояние объекта должно быть инициализировано внутри метода initialize, то есть конструктора.


class Person
  def initialize (name, age) # "Конструктор"
    @name = name
    @age = age
  end
  def get_info
    @additional_info = "Интересно"
    "Имя: #{@name}, возраст: #{@age}"
  end
end

person1 = Person.new("Джо", 14)
p person1.instance_variables # [:@name, :@age]
puts person1.get_info # => Имя: Джо, возраст: 14
p person1.instance_variables # [:@name, :@age, :@additional_info]

Доступ к данным

Переменные экземпляров являются приватными, они не могут быть доступны вне класса.

Методы имеют публичный доступ по умолчанию.

Для доступа к переменным экземпляров нужно определить методы «getter» / «setter».


class Person 
  def initialize (name, age) # Конструктор
    @name = name 
    @age = age 
  end 
  def name # name getter
    @name 
  end 
  def name= (new_name) # name setter
    @name = new_name 
  end 
end 

person1 = Person.new("Джо", 14) 
puts person1.name # Джо
person1.name = "Майк" 
puts person1.name # Майк
# puts person1.age # undefined method `age' for #<Person:

Часто логика getter/setter довольно проста: получить существующее значение / установить новое значение.

Но должен быть путь еще проще, чем фактическое определение методов getter/setter.

Так что можно использовать синтаксис attr_*:

  • attr_accessor — getter и setter
  • attr_reader — только getter
  • attr_writer — только setter

class Person 
  attr_accessor :name, :age # геттеры и сеттеры для name и age
end 

person1 = Person.new 
p person1.name # => nil 
person1.name = "Майк" 
person1.age = 15 
puts person1.name # => Майк 
puts person1.age # => 15 
person1.age = "пятнадцать" 
puts person1.age # => пятнадцать

В этом примере есть две небольшие проблемы:

  • При создании, Person находится в неинициализированном состоянии (без name или age)
  • Скорее всего, мы хотели бы контролировать максимальный установленный возраст

Чтобы решить это, нужно использовать конструктор и более разумный setter для age.

Но для начала мы должны рассмотреть self.

self

Внутри метода экземпляра, self относится к самому объекту.

Обычно, использование self для вызова других методов того же экземпляра является чуждым.

Но иногда вызов self является обязательным, к примеру когда он может означать назначение локально переменной.

Вне определения метода экземпляра, self относится к самому классу.


class Person 
  attr_reader :age 
  attr_accessor :name 

  def initialize (name, ageVar) # Конструктор
    @name = name 
    self.age = ageVar # вызываем age= метод
    puts age 
  end 
  def age= (new_age) # более разумный setter для age
    @age = new_age unless new_age > 120
  end 
end 

person1 = Person.new("Ким", 13) # => 13 
puts "Мой возраст #{person1.age}" # => Мой возраст 13 
person1.age = 130 # Попытка изменить возраст
puts person1.age # => 13 (setter не дал провести изменение)

Далее мы поговорим о методах/переменных и наследовании классов.

Наследование классов

Мы рассмотрим:

  • Оператор ||
  • Методы классов и операторы классов
  • Наследование классов

Оператор ||

Оператор || оценивает левую сторону.

  • Если true — возвращает ее
  • В другом случае, возвращает правую сторону
  • @x = @x || 5 в первый раз возвратит 5, в следующий — @x

Краткая форма:

  • @x ||= 5 — то же, что и сверху

var = var || что-то

Давайте посмотрим чем это может быть полезно.


class Person 
  attr_reader :age 
  attr_accessor :name 
  
  def initialize (name, age) # Конструктор
    @name = name 
    self.age = age # вызывает age= метод
  end 
  def age= (new_age) 
    @age ||= 5 # устанавливаем 5 только для первого раза
    @age = new_age unless new_age > 120 
  end 
end 
person1 = Person.new("Ким", 130) 
puts person1.age # => 5 (по умолчанию) 
person1.age = 10 # изменяем на 10 
puts person1.age # => 10 
person1.age = 200 # Пытаемся изменить на 200
puts person1.age # => 10 (всё еще)

Методы и переменные классов

Давайте поговорим методах классов и переменных классов.

Они вызываются на классе, а не экземпляре класса. Так что если вы знакомы с концептом метода static в Java, это его эквивалент.

Self вне определения метода относится к объекту Class.

Как мы увидим, есть еще два способа определить методы класса в Ruby, кроме self вне определения метода.

Переменные класса начинаются на @@.

Единичный символ @ дал бы вам переменную экземпляра, как уже упоминалось, а двойной символ даст переменную класса.


class MathFunctions 
  def self.double(var) # 1. Используем self 
    times_called; var * 2; 
  end 
  class << self # 2. Используем << self 
    def times_called 
      @@times_called ||= 0; @@times_called += 1 
    end 
  end 
end 
def MathFunctions.triple(var) # 3. Вне класса
  times_called; var * 3 
end

# Без создания экземпляра!
puts MathFunctions.double 5 # => 10 
puts MathFunctions.triple(3) # => 9 
puts MathFunctions.times_called # => 3 

Наследование класса

Каждый класс неявно наследуется от Object. Сам Object наследуется от BasicObject.

Множественного наследования нет, вместо этого используются примеси (mixins).

Давайте взглянем на небольшой пример.


class Dog # неявно наследует Object
  def to_s
    "Собака"
  end
  def bark
    "лает громко"
  end
end
class SmallDog < Dog # означает наследование, наследует класс Dog
  def bark # Перезаписываем
    "лает тихо"
  end
end

dog = Dog.new # (кстати, new это метод класса)
small_dog = SmallDog.new
puts "#{dog}1 #{dog.bark}" # => Собака1 лает громко
puts "#{small_dog}2 #{small_dog.bark}" # => Собака2 лает тихо

Модули

Теперь рассмотрим модули.

  • Модули
    • Как пространства имен
    • Как примеси
  • Использование встроенных в Ruby модулей, особенно Enumerable
  • require_relative

В общем-то, модули являются контейнерами для классов, методов и констант. Или других модулей.

Они походи на Классы, но не могут быть инстанцированы.
Class наследует Module и добавляет new.

Модули полезны для двух целей: пространства имен и примеси.

Модуль как пространство имен


module Sports
  class Match # Класс match как часть модуля Sports
    attr_accessor :score
  end
end

module Patterns
  class Match # Класс match как часть модуля Patterns
    attr_accessor :complete
  end
end

# Итак, мы имеем два разных Match

match1 = Sports::Match.new # заметьте использование оператора ::
match1.score = 40; puts match1.score # => 40

match2 = Patterns::Match.new
match2.complete = true; puts match2.complete # => true

Модуль как примеси

Другой способ использования модулей — это примеси.

В ОО-программировании есть интерфейсы. Они могут определять что класс «может» делать.

Примеси идут немного дальше и позволяют разделять (примешивать) готовый код между несколькими классами.

Кроме того, вы можете включить встроенные модули как Enumerable, которые могут сделать трудную работу за вас.


module SayMyName
  attr_accessor :name
  def print_name
    puts "Имя: #{@name}"
  end
end

class Person
  include SayMyName # позволяет включить функциональность этого модуля
end
class Company
  include SayMyName
end

person = Person.new
person.name = "Джо"
person.print_name # => Имя: Джо
company = Company.new
company.name = "Google & Microsoft LLC"
company.print_name # => Имя: Google & Microsoft LLC

Модуль Enumerable

Что действительно интересно, вы можете включить встроенные модули из самого Ruby. Когда мы говорили о массивах, мы затрагивали, что они имеют такие методы как map, select, reject, detect и т.д.

Дело в том, что массивы включают модуль Enumerable и это то что дает им такую функциональность.

Вы можете включить его в свой собственный класс.

Все что вам для этого нужно — обеспечить реализацию метода each. Как только вы это сделаете, вся функциональность Enumerable будет доступна!

Давайте взглянем на пример.
Скажем, у вас есть команда. Вы можете записать несколько классов в одном файле или сделать несколько файлов — оба метода полностью валидны.


# имя файла - player.rb

class Player

  attr_reader :name, :age, :skill_level

  def initialize (name, age, skill_level)
    @name = name
    @age = age
    @skill_level = skill_level
  end

  def to_s
    "<#{name}: #{skill_level}(SL), #{age}(возраст)>"
  end

end


# team.rb

class Team
  include Enumerable # Множество функций

  attr_accessor :name, :players
  def initialize (name)
    @name = name
    @players = []
  end
  def add_players (*players) # splat
    @players += players
  end
  def to_s
    "#{@name} team: #{@players.join(", ")}"
  end
  def each
    @players.each { |player| yield player }
  end
end


require_relative 'player' # require_relative позволяет включать другие .rb файлы
require_relative 'team'

player1 = Player.new("Боб", 13, 5); player2 = Player.new("Джим", 15, 4.5)
player3 = Player.new("Майк", 21, 5) ; player4 = Player.new("Джо", 14, 5)
player5 = Player.new("Скотт", 16, 3)

red_team = Team.new("Красная команда")
red_team.add_players(player1, player2, player3, player4, player5) # (splat)

# выбирает только игроков от 14 до 20 and отклоняет всех ниже уровня SL 4.5
elig_players = red_team.select {|player| (14..20) === player.age }
                       .reject {|player| player.skill_level < 4.5}
puts elig_players # => <Джим: 4.5(SL), 15(возраст)>
		  # => <Джо: 5(SL), 14(возраст)>

Области

Теперь рассмотрим области.

  • Области переменных
  • Области констант
  • Как область работает с блоками

Методы и классы начинают новую область для переменных.

Внешние переменные не имеют отношения к внутренней области.

Всегда можно использовать метод local_variables, чтобы посмотреть какие переменные входят (а какие — нет) в текущую область.


v1 = "outside" # вне области

class MyClass 
  def my_method
  	# p v1 Выдается исключение - нет такой переменной
    v1 = "inside" # внутри
    p v1
    p local_variables 
  end 
end 

p v1 # => outside
obj = MyClass.new 
obj.my_method # => inside 
              # => [:v1] 
p local_variables # => [:v1, :obj]
p self # => main

Область: константы

  1. Константа — это любая ссылка, которая начинается с заглавной буквы, включая классы и модули.
  2. Правила области константы отличаются от правил области переменных.
  3. Внутренняя область может использовать константы, определенные вне этой области, и также может перезаписывать внешние константы.
    • При этом внешнее значение остается неизменным за ее пределами

Давайте взглянем на пример.


module Test
  PI = 3.14
  class Test2
    def what_is_pi
      puts PI
    end
  end
end
Test::Test2.new.what_is_pi # => 3.14

module MyModule
  MyConstant = 'Внешняя константа'
  class MyClass
    puts MyConstant # => Внешняя константа
    MyConstant = 'Внутренняя константа'
    puts MyConstant # => Внутренняя константа
  end

  # остается неизменной вне области
  puts MyConstant # => Внешняя константа
end

Область: блоки

Блоки, в отличие от методов, наследуют внешнюю область.

Блоки закрыты. Они запоминают контекст в котором они были определены и используют этот контекст при каждом вызове.

Посмотрим на пример для блоков.


class BankAccount
  attr_accessor :id, :amount
  def initialize(id, amount)
    @id = id
    @amount = amount
  end
end

acct1 = BankAccount.new(123, 200) # id, сумма
acct2 = BankAccount.new(321, 100)
acct3 = BankAccount.new(421, -100)
accts = [acct1, acct2, acct3]

total_sum = 0 # объявляем переменную
accts.each do |eachAcct| # считаем общую сумму в блоке
  total_sum += eachAcct.amount
end

# та же область
puts total_sum # => 200

Блок – локальная область

Даже хотя блоки разделяют внешнюю область, переменная созданная внутри блока доступна только этому блоку.

Параметры блока всегда локальны для него, даже если они имеют то же имя, что и переменные внешней области.

Вы можете явно указывать локальные для блока переменные после двоеточия в списке параметров блока.


arr = [5, 4, 1] 
cur_number = 10 
arr.each do |cur_number| 
  some_var = 10 # Не доступна вне блока
  print cur_number.to_s + " " # => 5 4 1 
end 
puts # отдает пустую строку
puts cur_number # => 10 

adjustment = 5 
arr.each do |cur_number;adjustment| 
  adjustment = 10 
  print "#{cur_number + adjustment} " # => 15 14 11 
end 
puts 
puts adjustment # => 5 (Не затронута блоком)

Контроль доступа

Теперь рассмотрим:

  • Три уровня контроля доступа
  • Управление доступом
  • Насколько приватен приватный доступ?

При проектировании класса, важно подумать о том как много из него будет раскрыто миру.

Инкапсуляция: старайтесь спрятать внутреннее представление объекта, чтобы позже вы могли изменить его.

Всего Ruby предоставляет три метода контроля доступа: public, protected и private.

Инкапсуляция

В примере ниже детали рассчета рейтинга сохраняются внутри класса.


class Car
  def initialize(speed, comfort)
    @rating = speed * comfort
  end

  # Нельзя задать rating извне
  def rating
    @rating
  end
end

puts Car.new(4, 5).rating # => 20

Назначение контроля доступа

В Ruby есть два пути назначения контроля доступа:

  1. Указать public, protected или private
    • Всё до следующего ключевого слова управления доступом будет этого уровня доступа
  2. Определить методы обычным методом и затем указать public, protected или private, и перечислить через запятую методы под этими уровнями, используя символы методов

По умолчанию (если вы не указываете ни один из уровней), методы являются public.

Итак, взглянем на пример такого назначения.


class MyAlgorithm
  private
    def test1
      "Приватный"
    end
  protected
    def test2
      "Защищенный"
    end
  public
    def public_again
      "Публичный"
    end
end

# Второй способ:

class Another
  def test1
    "Приватный, как объявлено ниже"
  end
  private :test1
end

Что означают публичный, защищенный и приватный уровни?

public методы — нет установленного управления доступом. Кто угодно может вызывать эти методы.

protected методы — могут быть вызваны объектами определяющего класса или его подклассами.

private методы — не могут быть вызваны с явным получателем.
Исключение: установка атрибута может быть вызвана с явным получателем.

Приватный доступ


class Person
  def initialize(age)
  	self.age = age # Валидно - исключение
  	puts my_age
  	# puts self.my_age # Не валидно
  	                   # Нельзя использовать self на любом другом получателе
  end

  private 
    def my_age
    	@age
    end
    def age=(age)
      @age = age
    end
end

Person.new(24) # => 24