Сокрытие информации в Go
Часть раздела Объектно-ориентированное программирование путешествия по GO на Coddy. Урок 50 из 107.
Сокрытие информации выходит за рамки простого объявления полей неэкспортируемыми. Речь идёт о проектировании типов так, чтобы внешний код знал только то, что ему необходимо знать. И ничего больше.
Цель состоит в том, чтобы раскрыть поведение через методы, сохраняя внутреннюю структуру полностью скрытой. Consider такой подход:
type Counter struct {
value int
maxLimit int
}
func NewCounter(limit int) *Counter {
return &Counter{maxLimit: limit}
}
func (c *Counter) Increment() bool {
if c.value >= c.maxLimit {
return false
}
c.value++
return true
}
func (c *Counter) Value() int {
return c.value
}
Внешний код не знает, что Counter использует внутри int. Это мог бы быть float64, срез или что-то совершенно другое. Реализацию можно изменить, не затрагивая код, который использует Counter, при условии, что методы ведут себя таким же образом.
Этот принцип также применяется к helper functions. Сохраняйте internal logic неэкспортируемой:
// Экспортируемый - часть публичного API
func (c *Counter) Reset() {
c.value = 0
}
// неэкспортируемый - внутренний помощник
func (c *Counter) isAtLimit() bool {
return c.value >= c.maxLimit
}
Скрывая isAtLimit(), вы можете впоследствии изменить или удалить его. Сокрытие информации создаёт чёткую границу между тем, что ваш пакет обещает внешним пользователям, и тем, как он работает internally.
Задание
ЛегкоДавайте создадим secure wallet system, демонстрирующую information hiding. Вы создадите digital wallet, в котором отслеживание внутреннего balance и история транзакций полностью скрыты от внешнего кода: через методы предоставляется только поведение.
Вы организуете свой код в двух файлах:
wallet.go: Create структуруWallet, которая скрывает всё своё внутреннее состояние. Кошелёк должен отслеживать current balance и лимит расходов внутри, но внешний код не должен знать, как они хранятся или представлены. Предоставьте поведение через следующие методы:NewWallet(limit float64) *Wallet— constructor, который creates кошелёк с указанным лимитом расходов и нулевым balanceDeposit(amount float64) bool— adds деньги в кошелёк и возвращаетtrue, если операция выполнена успешно (amount должен быть положительным)Spend(amount float64) bool— списывает деньги, если существует достаточный balance AND amount не превышает лимит расходов; возвращаетtrue, если операция выполнена успешноBalance() float64— возвращает current balanceStatus() string— возвращает сообщение о состоянии в формате:Balance: $[balance] (Limit: $[limit])
main.go: Считайте конфигурацию кошелька и транзакции из входных данных, Create кошелёк, выполните операции и отобразите результаты после каждого действия.
Будут предоставлены следующие inputs:
- Строка 1: лимит расходов
- Строка 2: сумма пополнения
- Строка 3: сумма первой покупки
- Строка 4: сумма второй покупки
После создания кошелька выведите его initial status. Затем выполните каждую операцию и выведите либо обновлённый status (если операция выполнена успешно), либо Transaction failed (если операция была отклонена). Форматируйте все суммы в долларах с двумя знаками после десятичной точки.
Например, для 50, 100, 30 и 80 результат должен быть таким:
Balance: $0.00 (Limit: $50.00)
Balance: $100.00 (Limit: $50.00)
Balance: $70.00 (Limit: $50.00)
Transaction failedПоследняя транзакция завершается неудачно, потому что $80 превышает лимит расходов $50, несмотря на то что balance достаточен. Кошелёк применяет свои правила внутренним образом, не раскрывая, как именно он принимает эти решения. Внешний код просто вызывает методы и получает результаты, никогда не узнавая внутренние implementation details.
Попробуйте сами
package main
import (
"fmt"
)
func main() {
// Чтение входных данных
var limit float64
var depositAmount float64
var spend1 float64
var spend2 float64
fmt.Scanln(&limit)
fmt.Scanln(&depositAmount)
fmt.Scanln(&spend1)
fmt.Scanln(&spend2)
// TODO: Создать новый кошелёк с заданным лимитом расходов
// TODO: Вывести начальный статус
// TODO: Perform deposit and print result (status or "Transaction failed")
// TODO: Выполнить первую трату и вывести результат
// TODO: Выполнить вторую трату и вывести результат
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП в Go
Внешние файлыРабочее пространство и модули GoПакеты и импортыЭкспортируемые и неэкспортируемые именаВведение в ООП в GoСтруктуры как классыОпределение методов структурПолучатели-указатели и получатели-значенияИнициализация структурФункции-конструкторыИтоги — Простой калькулятор4Интерфейсы
Введение в интерфейсыНеявная реализацияИнтерфейс как контрактПустой интерфейс (any)Утверждение типаПереключатель типовКомпозиция интерфейсовИнтерфейсы Stringer и ErrorПовторение: Калькулятор фигур7Инкапсуляция
Экспортируемые и неэкспортируемые поляИнкапсуляция на уровне пакетовГеттеры и сеттерыСокрытие информации в GoИтоги — Записи о студентах10Обобщения (Generics) (Go 1.18+)
Введение в GenericsПараметры типовОграничения типовОбобщенные структурыОбходной путь для обобщенных методовИтоги — Обобщенная коллекция13Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн Abstract FactoryПаттерн ObserverПаттерн Strategy2Глубокое погружение в типы и структуры
Базовые и составные типыОпределение пользовательских типовТеги структурАнонимные структурыВложенные структурыНулевые значения и значения по умолчаниюПовторение — Контактная книга5Композиция вместо наследования
Почему в Go нет наследованияОсновы встраивания структурПродвижение методовВстраивание нескольких структурВстраивание против агрегацииЗатенение встроенных методовИтоги — Иерархия сотрудников8Обработка ошибок и ООП
Интерфейс errorПользовательские типы ошибокОбертывание ошибок (fmt.Errorf)Sentinel-ошибкиerrors.Is() и errors.As()Panic, Defer и RecoverИтоги — Парсер файлов11Стандартная библиотека и ООП
io.Reader и io.Writersort.InterfaceИнтерфейс fmt.Stringerencoding/json со структурамиИнтерфейс http.HandlerПовторение: модели REST API14Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикMiddleware как Декоратор3Указатели и память
Основы указателей в GoУказатели на структурыПередача по значению и по ссылкеФункция new()Сборка мусора в GoПовторение: Конструктор связного списка6Полиморфизм в Go
Полиморфизм через интерфейсыУтиная типизация в GoПравила реализации интерфейсовПолиморфные коллекцииВнедрение зависимостейИтоги — Обработчик платежей9Конкурентность и ООП
Основы горутинКаналы и взаимодействиеБуферизованные и небуферизованные каналыОператор selectsync.Mutex и sync.RWMutexsync.WaitGroupПроектирование потокобезопасных структурПовторение — Worker PoolПотренируйтесь самостоятельно: Онлайн-компилятор Go