Введение
Шаблоны проектирования — это лучшие практики, обобщенные многими программистами. Я изучил шаблоны проектирования, когда впервые начал писать проект, и активно или пассивно использовал их в процессе разработки. Хотя написание кода теперь не уточняет конкретно, какой шаблон проектирования используется, но тонкое написание признанного передового кода, в конце концов, более освежает взгляд. Есть несколько причин, по которым мы снова обращаемся к шаблонам проектирования: во-первых, многие превосходные исходные коды в основном используют шаблоны проектирования, а четкие шаблоны проектирования позволяют лучше рассматривать исходный код. Во-вторых, многие концепции проектирования промежуточного программного обеспечения также основаны на шаблонах проектирования, а в других языках есть свои лучшие практики проектирования. Для меня шаблоны проектирования начинаются с java и не заканчиваются java. В-третьих, с такими нормами вы можете лучше общаться с другими и быть краткими.
Принципы шаблонов проектирования
Многие замечательные статьи и книги объясняют это очень ясно, и я поделюсь своим собственным опытом. 1. Принцип единой ответственности, то есть класс отвечает только за одну вещь. Таким образом можно добиться эффекта развязки, код выглядит более освежающим, а также отражает инкапсуляцию java. Существует также принцип, называемый Законом Деметры, то есть объект имеет как можно меньше знаний о другом объекте, что также является вопросом разделения. 2. Принцип замещения Лискова и принцип взаимозависимости ведет к наследованию. То, что может делать родительский класс, может делать и дочерний класс, а дочерний класс может пытаться полагаться на родительский класс в своих действиях; но, наоборот, родительский класс не может полагаться на дочерний класс в некоторых своих действиях. Он отражает характеристики наследования java. 3. Принцип изоляции интерфейса, интерфейс также должен быть максимально изолирован. На самом деле классов написано слишком много, а сцепление действительно низкое.Чтобы позволить им общаться, наиболее часто используемым интерфейсом является интерфейс.Ведь вам нужно только знать, что делать и как это делать, и перейти к определенному классу. 4. Принцип открытого и закрытого, закрытого для модификации, открытого для расширения. То есть код должен обладать хорошей пластичностью и не должен повреждать исходную структуру кода.
режим создателя
Шаблон Creator предназначен для элегантного создания классов, которые мы используем.
1. Простой заводской шаблон
Этот используется реже, там есть фабрика, которая говорит вам, что я хочу, и вы можете построить ее и отдать мне. Например:
public interface Ball {
public String create();
}
public class Soccer implements Ball {
@Override
public String create() {
return "give you a soccer";
}
}
public class BasketBall implements Ball {
@Override
public String create() {
return "give you a basketBall";
}
}
public class EasyBallFactory {
public static Ball createBall(String name){
if (name.equals("basketball")){
return new BasketBall();
}else if(name.equals("soccer")){
return new Soccer();
}else {
return null;
}
}
public static void main(String[] args) {
Ball soccer = EasyBallFactory.createBall("soccer");
Ball basketBall = EasyBallFactory.createBall("basketball");
System.out.println(soccer.create()); //give you a soccer
System.out.println(basketBall.create()); //give you a basketBall
}
}
2. Заводской режим
На самом деле, это мало чем отличается от простой фабричной модели, которая заключается в том, чтобы продолжать дробить фабрику.Например, EasyBallFactory сейчас является общей фабрикой, и теперь мы разделяем ее на SoccerFactory и BasketBallFactory для производства футбольных и баскетбольных мячей соответственно. . Фабрика может производить разные товары в зависимости от спроса, например, футбол может производить разные размеры.
public interface BallFactory {
public Ball create();
}
public class SoccerFactory implements BallFactory {
@Override
public Ball create() {
//do something
return null;
}
}
public class BasketBallFactory implements BallFactory {
@Override
public Ball create() {
//do something
return null;
}
}
3. Абстрактная фабрика
Шаблон абстрактной фабрики в основном разрабатывает концепцию группы продуктов, то есть некая фабрика производит серию вспомогательных продуктов. Например, производя футбольные мячи, SoccerFactory также может выпускать футбольные журналы.
public interface Journal {
public String create();
}
public class SoccerJournal implements Journal{
@Override
public String create() {
return "give you a Soccer Journal...";
}
}
public class SoccerFactory implements BallFactory {
@Override
public Ball create() {
return new Soccer();
}
public Journal createJournal(){
return new SoccerJournal();
}
}
4. Одноэлементный шаблон
Существует много форм одноэлементного шаблона, и лучше всего сделать два суждения, чтобы гарантировать, что только одно из них является новым. Синглтон можно назвать очень распространенным шаблоном проектирования. Синглтон означает, что в жизненном цикле сервисного контейнера может быть только один такой экземпляр. Например, Servlet, Bean, внедренный в Spring, и т. д. — все это синглтоны.
public class ShiroUtils {
public static Session session;
public static Session getSession() {
if (session == null){
synchronized(ShiroUtils.class){
if (session == null){
session = SecurityUtils.getSubject().getSession();
}
}
}
return session;
}
}
5. Режим строителя
Создайте дистрибутив сложных объектов. Если супербольшой класс имеет много атрибутов, мы можем классифицировать атрибуты по разным категориям, сформировать немного меньший класс с другими атрибутами, а затем сгруппировать вместе несколько немного меньших классов. Например, компьютер можно разделить на несколько более мелких частей, таких как ЦП, материнская плата и дисплей. Есть больше компонентов для ЦП, материнской платы и дисплея, которые больше не подразделяются.
@Data
public class Computer{
private CPU cpu;//cpu 是个接口,有不同实现如InterCPU AMDCPU 等等
private MainBoard mainBoard;//mainBoard 是个接口,有不同的实现
private DisPlayer disPlayer;//disPlayer 是个接口,有不同的实现
}
public abstract class Builder {
abstract void buildCPU();
abstract void buildMainBoard();
abstract void buildDisPlayer();
abstract Computer createComputer();
}
public class XiaoMiBuilder extends Builder{
private Computer computer = new Computer();
@Override
void buildCPU() {
computer.setCpu(new InterCPU());
}
@Override
void buildMainBoard() {
computer.setMainBoard(new AMainBoard());
}
@Override
void buildDisPlayer() {
computer.setDisPlayer(new ADisPlayer());
}
@Override
Computer createComputer() {
return computer;
}
}
SpringBoot реализует конфигурацию 0, почти все конфигурации написаны в java-коде, большое количество конфигураций должны использовать режим построителя для классов конфигурации, поэтому уровень относительно ясен.
6. Режим прототипа
Паттерн прототип используется реже и применяется для создания дубликатов объектов. Если вам нужно реализовать Cloneable, вы можете переопределить метод clone(). Клон делится на поверхностный клон и глубокий клон. Неглубокий клон просто клонирует ссылку, объект по-прежнему один. Глубокий клон заключается в том, что объект также создает новый, как показано ниже:
@Data
@Builder
public class User implements Cloneable{
private String name;
private int age;
@Override
protected User clone() throws CloneNotSupportedException {
return new User(this.name,this.age);
}
public static void main(String[] args) throws CloneNotSupportedException {
User user1 = new User("pjjlt",25);
User user2 = user1.clone();
user1.setAge(18);
System.out.println(user2.getAge()); //25
}
}
Структурный образец
Приведенные выше шаблоны проектирования могут помочь нам очень элегантно создавать объекты.Давайте рассмотрим модели между несколькими объектными отношениями.
7. Режим прокси
АОП Spring использует динамический прокси. Почему бы вам не прочитать его динамически? Любой, кто использовал Spring, знает об этом. Просто посмотрите, как выглядит самая простая модель прокси. Прокси — это объект, который помогает другому объекту что-то сделать, и в то же время может немного помочь. Например, когда вы покупаете автомобиль, это правда, что вы купили автомобиль за деньги, но вы не можете напрямую поговорить с производителем, вы должны купить его через магазин 4S, а магазин 4S поможет вам со страховкой и налоговые вычеты, и, наконец, вы получите желаемый автомобиль.
public interface Buy {
public void buyCar();
}
public class People implements Buy {
@Override
public void buyCar() {
System.out.println("you get a car");
}
}
public class ProxyPeople implements Buy{
private People people;
public ProxyPeople(People people){
this.people=people;
}
@Override
public void buyCar() {
System.out.println("4s店帮你纳税、上保险...");
people.buyCar();
}
public static void main(String[] args) {
Buy buy = new ProxyPeople(new People());
buy.buyCar();
}
}
//输出:
4s店帮你纳税、上保险...
you get a car
8. Режим адаптера
Адаптеры, как следует из названия, позволяют двум несовместимым вещам работать вместе. Например, питание розетки 220 В, а мобильный телефон напрямую дает ему питание переменного тока 220 В 50 Гц.Я думаю, что он обычно взрывается (кроме Nokia...) Зарядное устройство мобильного телефона приспособлено для снижения напряжения , и мощность переменного тока становится мощностью постоянного тока. Помимо операций, требующих изменения свойств (желательно без примера), адаптеры используются и в наследовании интерфейсов. Предположим, интерфейс верхнего уровня имеет множество методов, которые должны быть реализованы классом.Я написал новый класс и просто хочу выбрать для реализации один или два интерфейса.Нужно ли мне реализовать все остальные методы, даже если это пустая реализация (простая реализация, без выполнения какой-либо логической операции), это то, что нам нужен класс адаптера, пустой для реализации этих методов, мой новый класс должен только наследовать этот класс адаптера, если я хочу реализовать определенный метод, я просто нужно переписать соответствующий метод класса конфигурации в порядке. Этот режим в основном используется, в конце концов, чей код не имеет интерфейса верхнего уровня.
public interface ATopIntf {
public void one();
public void two();
public void three();
}
public class Adapter implements ATopIntf {
@Override
public void one() { }
@Override
public void two() { }
@Override
public void three() { }
}
public class You extends Adapter {
@Override
public void one() {
super.one();
System.out.println("one");
}
}
9. Режим моста
Это разделение для абстракции и реализации. Это снова разъединение Кажется, что шаблоны проектирования учат нас, как изящно разъединять. Расширяемость кода улучшена, и может быть реализовано динамическое переключение кода. Затем используются первый Ball, Soccer и BasketBall, а также добавляются новые классы.
public class BallCut {
private Ball ball;
public Ball getBall() {
return ball;
}
public void setBall(Ball ball) {
this.ball = ball;
}
public void create(){
System.out.println(ball.create());
}
public static void main(String[] args) {
BallCut ballCut = new BallCut();
ballCut.setBall(new Soccer());
ballCut.create(); //give you a soccer
ballCut.setBall(new BasketBall());
ballCut.create(); //give you a basketBall
}
}
10. Декоративный режим
Декоративный класс, добавляющий немного больше функциональности исходному классу. Это похоже на режим прокси, я могу даже переместить весь код, и это все еще может иметь смысл. Смысл этих двух режимов немного отличается.Режим прокси означает, что исходный объект не может этого делать, а объекту-посреднику нужно разрешить это делать.Основное внимание уделяется объекту-посреднику, например, покупке автомобиля. Режим украшения означает, что исходный объект должен делать это напрямую, но функция усиливается, и доминантой является исходный объект. Например, посыпьте солью во время приготовления пищи.
11. Режим внешнего вида
Также известный как режим фасада, это фасад.Операция не должна давать объекту знать сложность своей внутренней реализации, и очень просто сделать так, чтобы пользователь максимально воспринимал ее. Вот почему мы стараемся, чтобы на уровне контроллера было меньше бизнес-логики (или ее определенное количество), пусть уровень контроллера просто играет функцию передачи параметров и проверки общих параметров, а остальное оставляем сервису. Нам также нужно постоянно инкапсулировать «длинный» код в метод в коде, чтобы «чтобы он везде хорошо выглядел». Давайте взглянем на написанный нами код, здесь он играет только роль передачи параметров, клиенту не нужно беспокоиться о том, как создавался этот футбол.
public static void main(String[] args) {
Ball soccer = EasyBallFactory.createBall("soccer");
System.out.println(soccer.create()); //give you a soccer
}
12. Комбинированный режим
Шаблон композиции предназначен для организации данных с некоторыми отношениями сдерживания вместе, типичным примером является древовидная структура. Например, функция меню, помимо собственных атрибутов, меню также может содержать подменю, которые можно создавать с помощью рекурсивных методов.
@Data
public class Menu{
private String name;
private int type;
private List<Menu> childMenus;
}
13. Режим наилегчайшего веса
Шаблон Flyweight позволяет пользователям максимально повторно использовать существующие объекты, тем самым избегая траты ресурсов на повторное создание объектов. Первое, что приходит на ум, — это пул соединений с базой данных и пул констант String. В более широком смысле почти весь код, связанный с кэшированием, будет в той или иной степени использовать шаблон легковеса. Паттерн Приспособленца требует, чтобы большинство объектов можно было экстернализовать. Вот две концепции. Атрибуты объектов-приспособленцев можно разделить на две части: внутреннее состояние и внешнее состояние. Внутреннее состояние относится к значению, которое не изменится в зависимости от среды, например личная информация. Значение изменения среды, информация которыми нельзя поделиться, например, курсы, которые посещает определенный студент колледжа.
public abstract class Flyweight {
//内部状态
private String name;
private String age;
//外部状态
private final String subject;
protected Flyweight(String subject) {
this.subject = subject;
}
//行为
public abstract void exam();
public String getSubject() {
return subject;
}
}
public class RealFlyweight extends Flyweight {
@Override
public void exam() {
System.out.println(this.getSubject()+" is examing...");
}
public RealFlyweight(String subject){
super(subject);
}
}
public class FlyweightFactory {
//定义一个池子
private static HashMap<String,Flyweight> pool= new HashMap();
public static Flyweight getFlyweight(String subject){
Flyweight flyweight =null;
if (pool.containsKey(subject)){
flyweight=pool.get(subject);
}else {
flyweight = new RealFlyweight(subject);
pool.put(subject,flyweight);
}
return flyweight;
}
public static void main(String[] args) {
System.out.println(pool.size());//0
getFlyweight("math");
System.out.println(pool.size());//1
getFlyweight("english");
System.out.println(pool.size());//2
getFlyweight("math");
System.out.println(pool.size());//2
}
}
поведенческая модель
Объекты создаются, и между объектами существует структурная связь.Надо посмотреть, как более изящно взаимодействовать.
14. Режим стратегии
Определите набор алгоритмов, инкапсулируйте каждый алгоритм и сделайте их взаимозаменяемыми. Можно сказать, что это инкапсуляция набора алгоритмов, и различные операции выполняются в соответствии с различными требованиями, заданными клиентом. Возьмем, к примеру, этот простой калькулятор.
public interface Strategy {
public int doOperation(int num1, int num2);
}
public class OperationAdd implements Strategy{
@Override
public int doOperation(int num1, int num2) {
return num1 + num2;
}
}
public class OperationSubstract implements Strategy{
@Override
public int doOperation(int num1, int num2) {
return num1 - num2;
}
}
public class Context {
private Strategy strategy;
public Context(Strategy strategy){
this.strategy = strategy;
}
public int executeStrategy(int num1, int num2){
return strategy.doOperation(num1, num2);
}
}
public class StrategyPatternDemo {
public static void main(String[] args) {
Context context = new Context(new OperationAdd());
System.out.println("10 + 5 = " + context.executeStrategy(10, 5));
context = new Context(new OperationSubstract());
System.out.println("10 - 5 = " + context.executeStrategy(10, 5));
}
}
15. Шаблон наблюдателя
Определено отношение зависимости "один ко многим". Когда состояние объекта (наблюдаемое) изменяется, все наблюдатели, которые зависят от объекта, будут уведомлены о выполнении связанных операций. Много промежуточного программного обеспечения основано на шаблоне наблюдателя, таком как RabbitMQ, и этих моделях, управляемых событиями (как это делает узел). Вот пример: наблюдаемое лицо является учителем-наблюдателем.По окончании времени экзамена все наблюдатели и студенты уведомляются о необходимости сдать экзаменационные работы.
@Data
public class Student {
private String name;
public Student(String name){
this.name=name;
}
public void dosomething(){
System.out.println(getName()+"交卷了");
}
}
public class Teacher {
private Set<Student> students = new HashSet<>();
public void addStudent(Student student){
students.add(student);
}
public void removeStudent(Student student){
students.remove(student);
}
public void doNotify(){
for(Student student:students){
student.dosomething();
}
}
public static void main(String[] args) {
Teacher teacher = new Teacher();
Student student = new Student("张三");
Student student1 = new Student("李四");
teacher.addStudent(student);
teacher.addStudent(student1);
teacher.doNotify();
}
}
16. Модель цепочки ответственности
Шаблон цепочки ответственности создает цепочку объектов-получателей для запроса, разделяя отправителя и получателя. Цепочка фильтров — это схема цепочки ответственности.
public abstract class Handler {
//下一个处理者
private Handler nextHandler;
public final Response handleMessage(Request request){
Response response =null;
if (this.getHandlerLevel().equals(request.getRequestLevel())){
response = this.echo(request);
}else {
if (this.nextHandler!=null){
//传递给下一个
response = this.nextHandler.handleMessage(request);
}else {
response =new Response()
}
}
return response;
}
public Handler getNextHandler() {
return nextHandler;
}
public void setNextHandler(Handler nextHandler) {
this.nextHandler = nextHandler;
}
protected abstract Level getHandlerLevel();
protected abstract Response echo(Request request);
}
17. Режим режима шаблона
Абстрактный класс предоставляет способ/шаблон, который определяет его методы. Его подклассы могут при необходимости переопределить реализацию метода, но вызовы будут выполняться так, как определено в абстрактном классе. SpringBoot инкапсулирует много кода наследования для пользователей, каждый из которых использует метод шаблона, например набор XXXtemplates.
public abstract class DBTemplate {
abstract void open();
abstract void select();
abstract void close();
//一个搜索模板
public final void selectTemplate(){
open();
select();
close();
}
}
public class MysqlDB extends DBTemplate {
@Override
void open() {
System.out.println("Mysql open...");
}
@Override
void select() {
System.out.println("Mysql select...");
}
@Override
void close() {
System.out.println("Mysql close...");
}
public static void main(String[] args) {
DBTemplate mysql = new MysqlDB();
mysql.selectTemplate();
}
}
18. Режим статуса
Проще говоря, объект имеет разные состояния и, в зависимости от состояния, может вести себя по-разному.
public interface State {
public void doAction(Context context);
}
public class StartState implements State {
public void doAction(Context context) {
System.out.println("Player is in start state");
context.setState(this);
}
public String toString(){
return "Start State";
}
}
public class StopState implements State {
public void doAction(Context context) {
System.out.println("Player is in stop state");
context.setState(this);
}
public String toString(){
return "Stop State";
}
}
public class Context {
private State state;
public Context(){
state = null;
}
public void setState(State state){
this.state = state;
}
public State getState(){
return state;
}
}
public class StatePatternDemo {
public static void main(String[] args) {
Context context = new Context();
StartState startState = new StartState();
startState.doAction(context);
System.out.println(context.getState().toString());
StopState stopState = new StopState();
stopState.doAction(context);
System.out.println(context.getState().toString());
}
}
19. Шаблон итератора
Предоставляет метод для последовательного доступа к элементам внутри объекта без знания внутренней структуры. В настоящее время это редко реализуется самостоятельно, в основном зрелые фреймворки или мощные JDK предоставляют методы доступа, такие как итератор в java. В основном это делается для дальнейшей инкапсуляции внутренней структуры объекта, чтобы поведение и структура были связаны. Это не пример.Все друзья, которые использовали итератор, должны знать, что для доступа к следующему элементу нужно перейти к следующему.
20. Командный режим
Командный режим заключается в том, чтобы обернуть запрос в объект в виде команды и передать его объекту.Вызывающий объект находит подходящий объект для обработки команды и передает команду соответствующему объекту, а объект ее выполняет . Проще говоря, разные запросы инкапсулируются в объект, и разные запросы вызывают разных исполнителей.
//真正干活的对象
public class Receiver {
public void doSomething(){
System.out.println("Receiver干活");
}
}
//命令对象
public abstract class Command {
public abstract void exectue();
}
//命令实现类
public class ConcreteComand extends Command{
//干活那个
private Receiver receiver;
public ConcreteComand(Receiver receiver) {
this.receiver = receiver;
}
@Override
public void exectue() {
this.receiver.doSomething();
}
public static void main(String[] args) {
Receiver receiver = new Receiver();
Command command =new ConcreteComand(receiver);
command.exectue();//Receiver干活
}
}
21. Режим заметок
Это эквивалентно созданию моментального снимка, записи состояния объекта без разрушения структуры самого объекта и восстановлению этого состояния при необходимости. Этот метод используется, когда база данных выполняет откат транзакции. Здесь следует отметить, что объект связан не с самим меморандумом, а с классом управления меморандумом (то есть List
@Data//备忘录
public class Memento {
private String state;
}
@Data //某对象
public class Originator {
private String state;
public Memento saveStateToMemento(){
return new Memento(state);
}
public void getStateFromMemento(Memento Memento){
state = Memento.getState();
}
}
//备忘录管理类
public class CareTaker {
private List<Memento> mementoList = new ArrayList<Memento>();
public void add(Memento state){
mementoList.add(state);
}
public Memento get(int index){
return mementoList.get(index);
}
public static void main(String[] args) {
Originator originator = new Originator();
CareTaker careTaker = new CareTaker();
originator.setState("State #1");
originator.setState("State #2");
careTaker.add(originator.saveStateToMemento());
originator.setState("State #3");
careTaker.add(originator.saveStateToMemento());
originator.setState("State #4");
System.out.println("Current State: " + originator.getState());
originator.getStateFromMemento(careTaker.get(0));
System.out.println("First saved State: " + originator.getState());
originator.getStateFromMemento(careTaker.get(1));
System.out.println("Second saved State: " + originator.getState());
}
}
22. Шаблон посетителя
При доступе к определенной роли к ней необходимо обращаться через посетителя. Когда вам неудобно напрямую обращаться к объекту, вам нужно дать свою ссылку посетителю и получить доступ к объекту через посетителя. Например, на уроке химии, если вы хотите увидеть структуру клетки, поскольку невооруженным глазом нельзя напрямую увидеть вещи в микроскопическом мире, вам нужно получить к ней косвенный доступ через микроскоп.
23. Паттерн посредника
Уменьшите сложность связи между объектами или вещами и уменьшите связь. Например, в распределенной системе это не операция, требующая обратной связи в реальном времени.Нам не нужно подключаться напрямую.Нам нужно только сообщить промежуточному программному обеспечению, что мы хотим сделать, а промежуточное программное обеспечение сообщает другой системе. Например, чтобы получить доступ (клик пользователя) к операции с новостями, вам нужно записать, кто к каким новостям обращался, добавить 1 к количеству просмотров новостей и обновить пользовательские настройки в режиме реального времени... Короче говоря, n таблиц базы данных должны быть обновлены и даже работают с несколькими данными промежуточного программного обеспечения, такими как ES, Mongo и т. д. Но для пользователя я просто выполняю операцию щелчка, я надеюсь, что результатом будет чтение новости, вы выполняете так много операций, это так медленно, пользовательский опыт очень плохой, а параллелизм также очень низкий, тогда лучше сделать Две маленькие системы, Система А, тянуть новости, пушить, и собирать сообщение и кидать в MQ middleware, ок, конец, пользователь видит новости. Затем система B слушает, получает это сообщение и выполняет различные обновления Здесь это промежуточное ПО является нашим посредником. В другом примере уровень управления в MVC является посредником между уровнем представления и уровнем модели. Например, следующий чат:
public class ChatRoom {
public static void showMessage(User user, String message){
System.out.println(new Date().toString()
+ " [" + user.getName() +"] : " + message);
}
}
@Data
public class User {
private String name;
public User(String name){
this.name = name;
}
public void sendMessage(String message){
ChatRoom.showMessage(this,message);
}
public static void main(String[] args) {
User robert = new User("Robert");
User john = new User("John");
robert.sendMessage("Hi! John!");
john.sendMessage("Hello! Robert!");
}
}
24. Режим переводчика
Создайте способ перевода языка или описания на язык или описание, которые мы хорошо понимаем. Хорошо понимать здесь означает понимать, видеть быстро. Первоначально я также хотел привести такой высокоуровневый компилятор как язык низкого уровня или даже механический язык и язык программирования высокого уровня, который мы используем. Поразмыслив над этим, на самом деле Map можно считать очень хорошим компилятором: в ключе можно хранить очень маленькую строку, а в значении теоретически можно хранить что угодно, поэтому код не пишется.
заключительные замечания
Хм, после долгого времени я, наконец, просмотрел эти шаблоны проектирования, и я чувствую, что у меня есть более глубокое понимание некоторых из них. Думайте об этой статье как о небольшом словаре для себя, о том, что вам нужно увидеть в будущем, нажмите, чтобы посмотреть, и, кстати, вы можете продолжать обогащать содержание. Некоторые вещи следует записывать самому, ведь хорошая память не так хороша, как плохое письмо. Снова почти одиннадцать, спать...