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