The second example is implementing what is known as the Observer Pattern. (the Observer) want to get notified about some event that occurred in the observed object X (the Observable/event source), right? Then you would tell the Observable to notify you. Or in other words, you start to listen to the observer for a notification - that's why the observer is also called listener.
The Observable doesn't know all it's subscribers. It only knows the listeners by their e.g., IObserver interface, which defines the actual notification callback. The Observable just adds the Observers to a container or collection.
The first example doesn't make sense. The observable calls a method on the observer to make him subscribe? That design flow must be weird.
Let's say you have a FileWriter class which exposes an event FileWriter.Completed. You have another class FileHandler that manages all files. The FileHandler calls FileWriter.write(filePath, data).
After each write operation the FileHandler has to show a message to the user. So the FileWriter is used by the FileHandler, but without knowing who's calling the write method. The FileHandler, one of the many consumers of FileWriter.write(string, string), knows that FileWriter implements an observable interface called IWriteToFileCompletedEventand defines the methodFileWriter.subscribeToWriteToFileCompleted(IObserver eventListener)`.
This is how the real implementation would look like in 'real life'
// The interface for the publisher or observable of a special event
interface IWriteToFileCompletedEvent {
void subscribeToWriteToFileCompleted(IWriteToFileCompletedListener observer);
void unsubscribeToWriteToFileCompleted(IWriteToFileCompletedListener observer);
}
// The interface for the observer of a special event
interface IWriteToFileCompletedListener {
void notify(string filePathOfCompletedFile);
}
// The observable that exposes a special event
class FileWriter implements IWriteToFileCompletedEvent {
private List<IWriteToFileCompletedListener> writeToFileCompletedListeners = new ArrayList<>();
public void subscribeToWriteToFileCompleted(IWriteToFileCompletedListener observer) {
this.writeToFileCompletedListeners.add(observer);
}
public void unsubscribeToWriteToFileCompleted(IWriteToFileCompletedListener observer) {
this.writeToFileCompletedListeners.remove(observer);
}
public void writeToFile(string filePath, string data) {
// Write data to file
// Once done notify all listeners of the write completed event
for (IWriteToFileCompletedListener observer : this.writeToFileCompletedListeners) {
observer.notify(filePath);
}
}
}
// The observer of a special event
class ContactsHandler implements IWriteToFileCompletedListener {
private FileWriter fileWriter = new FileWriter();
public void saveUserContactToFile(string filePath, string userInput) {
this.fileWriter.subscribeToWriteToFileCompleted(this);
this.fileWriter.writeToFile(filePath, userInput);
}
// Implementation of interface IWriteToFileCompletedListener
public void notify(string filePath) {
this.fileWriter.unsubscribeToWriteToFileCompleted(this);
this.messageDialog.show("The new contact was successfully saved to " + filePath);
}
}
// Another observer of a special event
class SettingsHandler implements IWriteToFileCompletedListener {
private FileWriter fileWriter = new FileWriter();
public void saveUserSettingsToFile(string filePath, string userSettings) {
this.fileWriter.subscribeToWriteToFileCompleted(this);
this.fileWriter.writeToFile(filePath, userSettings);
}
// Implementation of interface IWriteToFileCompletedListener
public void notify(string filePath) {
this.fileWriter.unsubscribeToWriteToFileCompleted(this);
this.messageDialog.show("The new settings were successfully saved to " + filePath);
}
}
The classes would interact like this
public void main(strng[] args {
SettingsHandler settingsHandler = new SettingsHandler();
ContactsHandler contactsHandler = new ContactsHandler();
// Imaging this method receives user input fromo the UI:
string newContact = textBox.gettext();
this.contactsHandler.saveUserContactToFile("C:\Contacts.txt", newContact);
// While waiting for the message to show the user adjusted some settings and clicked 'save'
string changedSettings = getChangedSettings();
this.settingsHandler.saveUserSettingsToFile("C:\UserSettings.txt", changedSettings);
// After a while the user sees the messages send from the event listeners.
}
Multiple objects of totally different type (SettingsHandler and ContactsHandler) but common base/interface type have subscribed to the same event of the event source. Everything is clean. The object that uses the event source object actually subscribes/listens. The class that can be observed, the observable, just notifies all listeners. There is no other interaction.
To wrap it up: your solution 1 is not convenient at all (although you can make it work) and produces ugly code.
Solution 2 is actually follows the pattern that describes how to implement events. This implementation is proved and is well established, since it achieves the goal of introducing events and produces clean and readable code.