Но в параллельной системе это может и не выполняться. При вызове объекты всегда обязаны находиться в допустимом состоянии, а они могут вызываться в самое неподходящее время. Вы обязаны убедиться, что объект находится в допустимом состоянии в любой момент, когда потенциально он может быть вызван. Зачастую эта проблема возникает с классами, которые определяют отдельные программы конструктора и инициализации (где конструктор не оставляет объект в инициализированном состоянии). Используя инварианты класса, обсуждаемые в разделе "Проектирование по контракту", вы сможете избежать этой ловушки.
Размышления о параллелизме и зависимостях, упорядоченных во времени, могут заставить вас проектировать более четкие интерфейсы. Рассмотрим библиотечную подпрограмму на языке С под названием strtok, которая расщепляет строку на лексемы.
Конструкция strtok не является поточно-ориентированной [33], но это не самое плохое, рассмотрим временную зависимость. Первый раз вы обязаны вызвать подпрограмму Strtok с переменной, которую вы хотите проанализировать, а во всех последующих вызовах использовать NULL вместо этой переменной. Если переменная принимает значение, отличное от NULL, программа повторно производит разбор содержимого буфера. Не принимая во внимание потоки, предположим, что вы собираетесь использовать Strtok для одновременного синтаксического анализа двух отдельных строк:
char buf1[BUFSIZ];
char buf2[BUFSIZ];
char *p, *q;
strcpy(bufl, "это тестовая программа");
strcpy(buf2, "которая не будет работать");
р = strtck(buf1," ");
q = strtok(buf2," ");
while (p && q) {
printf("%s %s\n", p, q);
p = strtok(NULL, " ");
q = strtok(NULL, " ");
}
Представленная программа работать не будет: существует неявное состояние, сохраняющееся в strtok между запросами. Вам придется использовать Strtok одновременно только с одним буфером.
Конструкция синтаксического анализатора строк на языке Java будет отличаться от указанной выше. Она должна быть поточно-ориентированной и представлять непротиворечивое состояние.
StringTokenizer st1 = new StringTokenizer("this is a test");
StrJngTokenlzer st2 = new StringTokenizer("this test will work");
while (st1.hasMoreTokens() && st2.hasMoreTokens()) {
System.out.println(st1.nextToken());
System.out.println(st2.nextToken());
}
Программа StringTokenizer обладает более четким и простым в сопровождении интерфейсом. Она не содержит в себе никаких сюрпризов и в будущем не приводит к появлению таинственных дефектов, чего нельзя сказать о программе Strtok.
Подсказка 41: При проектировании всегда есть место параллелизму
Развертывание
Как только вы спроектировали архитектуру с элементом параллельности, задача об управлении многими параллельными службами упрощается: модель становится всеобъемлющей.
Теперь вы можете проявить гибкость относительно способа развертывания приложения: по автономной модели, модели «клиент-сервер» или по n-звенной модели. Создавая архитектуру системы на основе независимых служб, вы также придаете динамизм процессу конфигурирования. Рассчитывая на параллелизм и разделяя операции во времени, вы получаете вес эти варианты, включая автономный вариант развертывания, где вы можете отказаться от параллелизма.
Другой путь (попытка внести параллелизм в непараллельное приложение) представляется намного сложнее. Если мы проектируем с учетом параллелизма, то со временем нам легче обеспечивать расширяемость и производительность, а если этот момент не настает, мы все равно получаем выгоду от более четкого интерфейса.
Так, может быть, пора?
• Проектирование по контракту
• Программирование в расчете на стечение обстоятельств
• Сколько задач вы выполняете параллельно, готовясь к работе? Можете ли вы выразить это с помощью диаграммы на языке UML? Можете ли вы найти иной, более быстрый способ подготовки к работе, придав своим действиям больший параллелизм?
29
Всего лишь визуальное представление
Каждый смертный все же видит
Только то, что хочет видеть,
Отметая остальное.
Ля-ля-ля…
Ранее нас учили не писать программы одним большим куском, а использовать принцип "разделяй и властвуй" и разбивать программу на модули. У каждого молу-ля есть свои собственные обязанности; модуль (или класс) считается четко определенным, если у него имеется одна четко обозначенная обязанность.