В чем разница между библиотекой, утилитой и компонентом

Библиотека — единица программного обеспечения, в которой отсутствует метод запуска.
Утилита — единица программного обеспечения, в которой метод запуска одновременно является и методом, выполняющим полезную работу.
Компонент — единица программного обеспечения, в которой метод запуска отделен от метода, выполняющего полезную работу.

Ниже рассмотрим три примера.

Библиотека:

Класс содержит полезный метод roll, но не содержит метод запуска, main. Бросок кубика через вызов java Dice невозможен — класс имеет смысл только как зависимость.

public class Dice {

    public int roll() {
        return (int) (Math.random() * 6 + 1);
    }
}

Ключевое отличие библиотеки — отсутствие метода запуска.

Утилита:

Класс содержит как полезный метод roll, так и метод запуска main; бросок кубика совмещен с запуском утилиты. По завершении выполнения метода main, теряется какая либо потенциальная возможность обратиться к методу roll.

public class Utility {

    public static void main(String[] args) {
        var dice = new Dice();
        dice.roll();
    }
}
class Dice {

    int roll() {
        return (int) (Math.random() * 6 + 1);
    }
}

Под потенциальной возможностью мы понимаем, что код в таком исполнении, хоть и выполняет полезную работу, но ее результатом никто не может воспользоваться. Чтобы потенциальную возможность превратить в фактическую, нужен потребитель результата — клиент, коим в данном случае может выступить стандартный поток вывода:

public static void main(String[] args) {
    ...
    System.out.println(dice.roll());
}

Компонент:

public class Component {

    public static void main(String[] args) {
        var dice = Dice.start();
    }
}
class Dice {
    private static Dice instance = new Dice();

    private Dice() {}

    static Dice start() {
        new Thread(() -> {
            synchronized (instance) {
                try {
                    instance.wait();
                } catch (InterruptedException ignored) {}
            }
        }).start();
        return instance;
    }

    public int roll() {
        return (int) (Math.random() * 6 + 1);
    }
}

Здесь требуется больше пояснений. Вызов метода запуска приводит к созданию нового фонового потока, который висит в памяти за счёт instance.wait(). Программа работает до её принудительного завершения, поэтому обращение к методу roll остаётся потенциально возможным на протяжении всего этого времени. Как и в предыдущем случае, чтобы потенциальная возможность превратилась в фактическую, нужен клиент, для чего мы соберем другой компонент, Player, который раз в секунду будет бросать кубик:

public class Component {

    public static void main(String[] args) {
        var dice = Dice.start();
        Player.start(dice);
    }
}
class Dice {
    <...>
}
class Player {
    private static Player instance;

    private final Dice dice;

    private Player(Dice dice) {
        this.dice = dice;
    }

    static Player start(Dice dice) {
        instance = new Player(dice);
        new Thread(() -> {               //
            while (true) {               // Попутно играет роль альтернативного
                try {                    // способа удержания программы в памяти
                    Thread.sleep(1000);  //
                } catch (InterruptedException ignored) {}
                System.out.println(dice.roll());
            }
        }).start();
        return instance;
    }
}

Альтернативным клиентом может выступать живой человек — тогда компонент должен предоставить канал связи, например HTTP. Однако важно отметить, что протокол передачи данных не является обязательным атрибутом компонента. Компонент, взаимодействие с которым происходит через протокол передачи данных, называется сервис 1.

Компонент необязательно является самостоятельным процессом: в одном процессе может быть запущено несколько компонентов. Ярким примером этого может быть модульный монолит.

Объекты классов библиотеки можно свободно создавать. Получить ссылку на объект компонента можно только через вызов метода запуска, то есть произвольное создание экземпляра класса невозможно. Интерфейс компонента можно выразить формулой Component.start(): Facade, где Facade открывает доступ к полезным методам.