Before we get proper into it, right here is the full runnable demo code repository for these who favor it.
Introduction:
One of the fantastic methods to enhance your code is through the use of sketch patterns. They assist us deal with some frequent issues in software program improvement and make our functions less complicated to recognize and maintain.
In this put up we’ll take a appear at combining three of the most famous sketch patterns: DAO, Factory technique and Singleton. This sample will enable us to create a separate classification for managing database information access, which makes it handy to reuse throughout initiatives or utility states.
If you suppose your utility is going to get complex , then you want to begin designing your challenge in a way that divides responsibilities, and makes it handy to add new features.
Design patterns are powerful equipment that assist us limit complexity, enhance readability and make our code extra bendy and reusable. They are now not a silver bullet however, and come with pro’s and con’s, so be certain to analyse the trade-offs for your specific use case.
Today, we’ll be combining the DAO, Factory approach and Singleton Design Patterns to summary our Flutter app’s persistence layer from the commercial enterprise common sense — ensuing in cleaner code that is without problems extensible.
The first issue we can do is summary our low degree information get right of entry to good judgment into a DAO. It is used to separate enterprise and persistence layers into unique tiers of the application, so they can be maintained independently.
The DAO sample is an abstraction of low degree information get right of entry to logic. It is used to separate enterprise and persistence layers into extraordinary tiers of the application, so they can be maintained independently. The fundamental purpose right here is encapsulation — imparting an API round your statistics storage layer that permits different factors to have interaction with it besides understanding how precisely they’re doing what they do (e.g., reading/writing).
Here is an implementation of the DAO sample in dart. Keep in mind, these are easy code excerpts, which are no longer supposed to be run — for a whole example please refer to the full code here: dao_factory.
First, outline the DAO Interface used to summary away decrease degree implementation details. This would be the entry factor from which your commercial enterprise common sense accesses the persistence layer, then again as we will see later, we will be including an extra Factory approach layer for our use case.
abstract class UserDAO {
/// Initialise dao
Future<void> init();
/// Returns a list of all persisted users
List<User> getAllUsers();
/// Returns a list of users with the given name
List<User> getUsersByName(String name);
/// Persists a user
void addUser(User user);
/// Returns the deleted user object if delete was successful, otherwise
/// will return null if user was not found.
User? deleteUser(User user);
/// Clears/Deletes all persisted users
Future<void> clear();
}
You would possibly have seen that we have no longer carried out any of the above techniques — as an alternative we have really described a ‘contract’ from which we are guaranteeing positive functionalities to the enterprise common sense layer. So, let’s go beforehand and outline an implementation with an arbitrary Flutter persistence provider, say Hive. The key right here is that our UserDAOHiveImpl type implements UserDAO, as a result we can safely anticipate all functionalities described in the UserDAO interface will be provided.
class UserDAOHiveImpl implements UserDAO {
late final Box<User> userBox;
/// Initialise the Hive DB here...
@override
Future<void> init() async {
await Hive.initFlutter('hiveDb');
Hive.registerAdapter(UserAdapter());
userBox = await Hive.openBox<User>('userBox');
}
}
@override
List<User> getAllUsers() {
List<User> users = userBox.toMap().values.toList();
return users;
}
@override
void addUser(User user) {
userBox.add(user);
}
@override
Future<void> clear() async {
await userBox.clear();
}
/// TODO: implement other UserDAO interface overrides...
}
Next, I will exhibit you how to flip this code into a manufacturing facility DAO, and reuse it with special databases for one of a kind situations.
Suppose you had been the use of Hive, as we have been above, but then you found ObjectBox: A super-fast object oriented database for mobile! Seeing the overall performance increases, you now choose to change to ObjectBox for your Ios & Android apps, whilst maintaining Hive for Web
The Factory Method sample is used to create new objects as determined through the factory. It is frequently considered as an choice to subclassing or delegation in object oriented programming. By the usage of the Factory Method pattern, you can without difficulty create objects besides having to specify the actual classification of that object inside the purchaser code itself.
You really name one feature which returns an occasion of any sort of type that you favor primarily based on its enter parameters. In easy words, it’s essentially simply a wrapper round some method(s) which are accountable for developing your cases with their personal good judgment internal them so that purchasers don’t have to worry about it anymore!
Following our hypothetical example, we will now use the Factory Method sample to return the right DAO relying on the modern platform. Just repeating again, these code snippets are for demo purposes, for runnable code, please refer to the full Github repository here: dao_factory.
class UserDAOFactory {
static Future<UserDAO> getUserDAO(DBType dbType) async {
UserDAO userDAO;
if (defaultTargetPlatform == TargetPlatform.ios || defaultTargetPlatform == TargetPlatform.android) {
userDAO = UserDAOObjectBoxImpl();
} else {
userDAO = UserDAOHiveImpl();
}
await userDAO.init();
return userDAO;
}
}
After defining the UserDaoFactory class, we can clearly name it from our commercial enterprise good judgment layer as beneath — be aware that it has no idea, and does no longer care what the underlying implementation that is in the end used is — which is the splendor of the Factory DAO pattern!
await UserDAOFactory.getUserDAO().addUser();
Additionally, if we desired to add but some other DAO implementation, we should do it with virtually no code modifications required to our commercial enterprise common sense layer, and minimal refactoring to the Factory class. This is additionally validated in the full Github repository.
Finally, we will put into effect the Singleton sketch sample to re-use our DAOs.
As you would possibly have seen in our UserDAOFactory class, we are presently returning new situations of our DAOs every time a name is made to the DAO thru the Factory. This is now not what we want, as a substitute we desire to re-use our already created DAO instance. Additionally, initialisation takes time, and we do no longer choose to be spending assets initialising our DAO with each and every call.
Enter the Singleton layout pattern, which ensures that solely one occasion of a classification exists at any given time (hence its name). The singleton sample forces one classification into being accessed statically i.e., there can solely be one instantiation per utility lifetime instead than a couple of instances.
But how do we create a singleton in Flutter?
To put into effect the Factory DAO with Singleton layout sample in Flutter, we want to make a few easy modifications to our DAO implementations to flip it into a Singleton, which I have proven beneath the usage of the equal UserDAOHiveImpl proven above:
class UserDAOHiveImpl implements UserDAO {
late final Box<User> userBox;
bool _isHiveInit = false;
/// ADD FROM HERE
static final UserDAOHiveImpl _instance =
UserDAOHiveImpl._privateConstructor();
/// Empty private constructor, as we are initialising the async init() in a
/// separate call.
UserDAOHiveImpl._privateConstructor();
factory UserDAOHiveImpl() {
return _instance;
}
/// TO HERE
/// .....Rest of UserDAO remains the same
}
As we can see, Dart has an in-built manufacturing facility key-word which makes it great easy to put into effect Singleton. Read extra about it here. Apart from the strains proven above, no modifications are required somewhere else in the code — the instantiation stays the same, however now a cached occasion will be again on subsequent calls.
Conclusion:
There we have it! We’ve proven how to put in force three frequent patterns in Flutter: DAO, Factory and Singleton with a actual use case. For the sake of brevity, we have left out finer small print such as integrating the commercial enterprise good judgment layer, and the use of Provider for State Management, so please see the full Github repository here: dao_factory for a entire example! We would be completely happy to make a similarly article on that if there is interest. :)
This is simply the tip of the iceberg when it comes to patterns; there are many extra out there that you may additionally come throughout as you discover specific approaches to architect your apps! Give it a strive in your subsequent app and experience free to drop a line to us with any feedback, remarks or questions.