Careful With “Singleton” Lookalikes (WAY TOO COMMON)

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hello Mike hey kayo we have a new question right tough one again I like these types of questions because they contain very few words and the topics are so deep that we could have made like a whole series to explain these things but let's try to give our best self in this episode ok the question is I have been looking for a robust way to write a testable networking layer and I'm always confused about when and when not to use single tones so two big topics here first is this networking layer the stability and then we have the singleton the single term is a design pattern very popular guess with all the open-source libraries out there so should we talk about singleton squares ok I have the definition actually of the singleton pattern from the design patterns book by gamma Johnson lascivious and help the Gogh book again go for and this is obviously very summed up but I think it gives the gist of the whole pattern so basically the authors say that the design pattern singleton is a way to make sure that the class has only one instance and it provides a single point of access to it the pattern specifies that the class should be responsible itself for keeping track of its own instance and it can further ensure that no other instance can be created by intercepting requests for creating new objects and provide a way to access the sole instance ok that's a lot yeah that's maybe put some code here and go like one by one right so if we talk about a networking later let's let's start with a classier API client ok so imagine we have an API client here and I can just create my client for example that's fine that's a normal class you defined your type we define the methods and properties in here the initializer and that's it now how can we make this a singleton so the key point here is that this class needs to have a single point of access first of all right so the whole point of a single term is to have only one instance of this class right to guarantee that to enforce it right so first of all I shouldn't be able to just create an API client like this exactly you can't create an API client like these but at the same time you need to provide her way to access an API client so first of all we can prevent people from creating if your clients by just making the initializer private that's correct now the type should give you access to one right and if you followed by the book we would end up with a private static instance and that's an API client and then we would have a static function get instance yeah that returns an API client that's it and you return here the instance okay so now from the outside world we can say get instance but in Swift yeah what we can do is we can just make this a static Lant instance because we cannot mutate it so we don't you have a getter the type system already enforces this for us so it's much simpler here we can just say instance boom we have access to the char instance and we guarantee that you can only have one instance per application run that's it so this is a single though this is a single damn well as it is right now and it doesn't do much so what if I make this class final then write the book though says you shouldn't be doing this because a single term may be extended in the future by sub classing the pattern is saying that we should allow extension for example adding more methods to the API client by having a subclass right right so we could have class my API client that inherits from DPR client and then we can add more methods to it yes or we can even override some behavior yeah you're gonna variety yeah but you can make a case that in Swift we can use extensions without using subclass so we can still make this final so we don't want our people to subclass but we have extensions to add more behavior we cannot override behavior but we can still add more behavior so it's a trade-off here if you want to allow people to change behavior of your class you allow sub classing otherwise you just allow them to use extensions and you can still add more behaviors by using the decorator pattern but we don't need to get there let's focus here so this looks good that's how we would implement a single tone you have only one instance that's what it means but if we go to for example URL session or shared which is a type provided by Apple and you see the documentation here for the shared instance it says that this method provides a shirt single trial session object but the good thing is that a king is to create a URL session myself yeah with different configurations as well this is just a convenience but of course this breaks the whole contract that the Ganga 4 referred to in their book right so what people don't really call this this is the singleton with capital ass all right it's the by-the-book implementation and you have the lowercase singleton which is like a convenience right okay in this case you have the shared type that is only a getter you cannot set the shared to point to another instance which means you still have like these unique instance per application run but you can still create your own if you want to you can even subclass your obsession you can do other things with it yeah and that's helpful for a plethora of subjects I guess yeah nothing bad with that yeah exactly especially if your library creator you have open source projects and you want people to use their libraries you want it to be easy to use but you also want them to if they want to customize those types they can still go there and create their own so this is the difference from the single tone with kept OS and the lower cases as people get confused with this because they think that this is the implementation of the single tone pattern by the book and it's not and then people start asking well well how can i test code that uses a single tone for example well it depends because there's nothing wrong with having a C and still have testable code so let's illustrate this with for example view controller like a log in your controller and you have a method deep tap login button and here you want to use the API client ok to logging to a service yes the single tone API client dot instance dot login right and you pass a logging request and you get a use it back for example right and then you do something after your login like show next screen now this would be hard to test first of all we need to have a function login it gets a closure for example love the user and let's define here our logging easier now if you want to test that logging the controller is working properly but you don't want to make an API request in your test you have a unit test right how can you override this call here how can you replace the API client with a mocked one to be honest personally I don't know anyway do that in Swift see probably object see you could result yeah getter and replace it with a mock but in this case since you cannot subclass API client and this is a let you cannot do it placing sweet yeah it sounds accurate but there are other choices we can make here so I want to be able to at least implicit because now I can have an API in here ok I see what you're doing so you're gonna have a property injection basically yes for example API client dot instance and now I don't access this directly which means in my test I can inject this with a mock API client for example the overrides this logging and something else right and you can intercept now the requests to this logging function and you can test whatever it is you that you need to test in your unit tests yes okay and then there's another thing that I see people doing it as well nothing wrong with it but again he breaks the pattern place you make this thing settable that's a big no-no well I don't know like I wouldn't do this if I'm exposing these two clients as a library creator but if I'm working a team with people I don't think there is any problem with that right that's a it's a big assumption right there I'm your son so now what I can do in my test here in this case is to inject the instance to be a mock API client and then I don't even need these anymore I can just go back to what it was right so now you introduce global State for the test target yes so probably in the setup I need to set up the mock client and in the teardown I need to remember to replace it back yeah so this is not a single anymore this is something else because it's not a single term with capitalize it's not a single term with lowercase s this is just global state mutable normal state beautiful global state yes the dark arts that's what it is but people still call it single tone because of the semantics it looks like a singleton it has a static instance he has a private initializer looks like a single tone it quacks like a duck right no I think that's very important what you mentioned there the semantics I feel that if developers see the word instant somewhere or shared instance or not shared something like that they tend to think that this is the singleton pattern but then there is no differentiation between the singleton and immutable global State so what is this yeah I think what we can do in this case is just to call this chart if you want to make immutable and what's the point of making this private right and I think that's even better because now this can go back to a lead so it's an immutable shared instance and if you want to create new instances of this API client well you have initializer now exactly so at least this global stage now is contained so this is back to lowercase as singleton and instance that is only create once you cannot be replaced but you can still create your own and pass it along yeah exactly so that's the difference from capital as singleton lowercase single tone and just mutable global shared state yeah it's ok let's fix the problems here what is now it's just shared but in this case if you want to test it you will need to have a property and now we can use property injection to replace it doing tests so we don't make API requests right and for example let's say that this is a Instagram like application and when they log in you go to the feed screen so let's expand this problem a little bit let's say we have a feed view controller now you know if you did load we're going to call load feet okay it stands to reason and when you load the feet of expect you get like loaded items and then you update UI for example okay so what would need here is also to have an API and add a new method to the API client write load feet with a completion and let's say it's an array of feed items right so we need to define our feed item here cool did you see the problem I implemented a new feature the feed blue controller or like a feed module and I had to add a new method to the shared type right and then the question is why is this a problem you know that's what what's what's wrong with that well let's have a look at the diagram so imagine you have API client and then you have the login and then you have a login function and then you have the feet and you have load feed function and if you put into the followers feature you have a load followers function but the logging doesn't care about feed or followers and the followers doesn't care about login or feed and the feed doesn't care about logging or followers but since this is shared you have these different modules sharing this type in the middle this concrete type API client and every time I need to add a new method here I need to recompile redeploy all the other modules because they depend on the concrete type so what's happening here is that we have source code dependency in this core API client layer so if I want to reuse the login in a different context in a different application how can I move the login module to a separate application without bringing the API client with me you can't use the answer well you can if you now move the API client to a shared module and then you move the login and you reference it right but then you make a change and then the other modules break as well yeah if you add another module for example upload videoes you need to add a new method here upload video and you break all the other modules you need to bump a version in this framework and now everyone else needs to update yeah the references and then you start having semantic versioning issues right but we can solve that yeah we can solve it I would just like to say here that this modular approach is not for all apps you know if something is small and you don't care about that if it's a pet project you know it's it's redundant it's excessive it's lost time basically it's wasteful it's wasteful yeah however if you care about modular design because of the business because of the requirements you should probably consider breaking these things into something much more responsible perfect we are trading off convenience with what we really need what this class needs is just you call a function right but now this class actually needs to know where to find its instance no its concrete pipe and then invoke a function right there are three layers here where you should need one and it creates this implicit dependency of this API client as well yes this global shared state here is very convenient yeah easy to create in to use but introduce some problems so what we're doing here is making a case that if you need to achieve some kind of real ability makes you think about these problems you might start just for convenience of accessing this type but in each understand the trade-offs so when this trade-off bites you you know what to do about it absolutely absolutely so a solution to this problem of every time you add a new method you might break other modules it's actually fairly simple initially right because this problem is not going to come in one hit normally your app start growing and then you need to start abstracting things as you need to and it normally happens in phases you start with these shared implementation and when it's time to for example get the login and put in a separate module you can just restructure your code a little bit to accommodate the need you don't need to introduce a bunch of protocols and a bunch of problems up front I think it's that's very good advice yeah and it's hard to do as well what we could do here in Swift is to use extensions for example we can have a generic method execute request they can execute any type of request URL request and then every module has its own API client extension with whatever method they need so let's say we are breaking this now in modules so this is the API module we don't need to smoke anymore then we have the login module for example and we have the feed module and what if we had like a generic function here execute and then it has a completion let's say it just completes with data for now we're not handling errors right so this API is enough to execute any get or post request for example maybe not enough for uploads and downloads but we don't have this requirement yet and then what we can do is move the log gain function to the login module for introducing an extension in a separate module to the API client with the login implementation we can even move the logged in user District to that module as well yeah that makes sense here we can do the anything for the feed module we have an extension an API client that is the load feet in the load feet and the logging method will call the execute the generic method and then we can move the feed item to the fifth module as well now we have this implementation where we specialized the client for the module and we have a generic client in the middle which even can make these whole API client think redundant we could have just used playing URL sessions by creating extensions on URL session or Alaba fire or whatever you use but if you want to create a nice facade for your networking layer you can use this and look how we broke the dependencies without breaking the implementation we just restructured change the little-bitty architecture the dependency graph but look how the attack client is still shared between all those modules so if at some point you need to add a new module where you want to upload data to the API you might have to add a new method here and you break the clients as well you're not completely safe from change but it's much more flexible now now this is stage one what if you will need a little bit more flexibility in a break in the client you make it is more reusable now we talked about inverting the dependency what if instead of the modules depending on a concrete API client we actually make the concrete API client depend on the modules right that's the second stage and this is gonna give us all the freedom that we want basically once we do it exactly and most applications I've seen 99% of the applications seen stop here right and eventually when this gets too messy the solution is to rewrite the application because that's what's gonna happen if you don't proactively understand where your architecture is moving to and change it as needed you don't need to start with a super abstract implementation you can move later by little yeah but you need to move you need to be proactive and you make those decisions as you go and at some point you might need to invert dependency because the API client is just a detail what is important to the business is not the API client implementation in API requests if you go to her boss and say are we having a problem with the request they don't care hi Rohit they care about the the features they don't care about the technicalities and the details that's why I prefer to have contained logic in modules and details depend on the features in this case the detail is the API client now this start open up a bunch of options here and to do so we introduce protocol or closures or any type of interface for the modules for example the logging in it's a function or any the type that implements a function by talking to an API the feed will need a function to load the feed the followers will need a function to load the followers and that's all they need it doesn't need to know where it comes from so when you have a concrete type dependency as we have here when the logging module directly references the API client we depend on the API client we need to know how to locate the instance in the shared pointer and then we need to invoke the function we want when all we want it's a function so why don't we just depend on a function or on a protocol to implements the function and now people start getting scary because okay this is not familiar this looks weird right so I think you make some very good points here first of all it doesn't look familiar because there is the specialization that you mentioned before so now what you're doing there basically is for the logging module I need something to log mean and I don't care about anything else so the ease of use we talked before by exposing a third instance of this API client and being able to use it anywhere without any constraints well that doesn't specialize thing because all modules have access to everything else however what do I need as a login module as a feed module as a followers module what do I need you need discipline to ask yourself that and you need to continue to answer this question and move on and actually implement that thing that's why I think we don't see it often and we hear so often about the rewrites for the features or the whole code bases yes and there's also good arguments against it because they say they pack clients should be very generic it just make API requests right this API client can be a URL session can be a lava fire or it can be your own facade and you're just saying this is the most generic thing you just execute requests and say okay you can still keep this whole separation you can go level 4 right and level 4 is you have your generic API client in the center you have your modules with only what they need and then you implement the adapters probably in your main module but it doesn't have to be you can have your adapter module we can have whatever you you need but now you start separating all the modules now this is completely modular because you can use this API client in other applications you can use the logging in other applications in other contexts you can use the followers module in other contexts as well you can just compose all those things together and when you're testing you compose it with mocks or you compose it with stubs you compose it with whatever you need absolutely you open up possibilities level-4 yeah as I mentioned before freedom and the bigger the codebase and the more complex it is the more complex the requirements are I think freedom is a tremendous asset to have on the other hand it doesn't come easily you need to be very disciplined to reach that stage and as we mentioned before if you're making a small application and you should definitely not start with the level 4 by the way these levels for the audience out there we just mentioned them as has a way to keep count for the slides right yes it's not a thing no it's not a thing it's a level of abstraction based on the slides that we have there but ok you're saying this is not easy and I agree this is not easy but I think this is simple it is simple in the grand scheme of things it means that you can compose those things simply you can change things simply you can test things simply and to be fair people look at this diagram and they compare with the first one we showed and they're like this is our engineer look how many things but the diagram doesn't tell the truth exactly it's not over as you see here the architecture is in the code so let's show the code that implements exactly that we still have the dependency on the API client but all we need is a login function and the login function is this so all we need here is it login function that gets a callback let's make this optional for now it needs to be injected of course this is a closure it's a closure where do you invoke with a closure callback something like that right now you can move the API client to a main module great now the feed all you need is a function or a protocol with that function so we have a load function where you pass a closure and now you move the load feed to the main module another main module just needs to compose the functions by passing it to the modules right and not just these functions the main module can compose any functions right so this is very important as well here because we just abstracted the API the remote part for these actions you know logging and load feed however these are just signatures of these functions right meaning that you can have a remote or a cast version for example yeah you're free to do whatever you want very simply and you have the same amount of code yeah absolutely so maybe you look at this diagram if you've intimidated but it's just because you cannot translate diagrams concept modularity into code sometimes it's not as difficult and we think it is yeah certainly not it just takes some time to fiddle around with these things and get it working definitely in my opinion test driving these things and working with protocols or closures for dependency injection I think that's a great way to become better improve so now how would you test this how easy would it be to just inject a function and expect something to happen right that's it yeah in the feed view controller how easy would it be yeah it doesn't need to be a view controller of course you could be using a view model in here right it's gonna be anything right in a view model and then you have like a load function yeah it can be a service a service exactly it can be other things it doesn't need to be a view controller let's say it's a feed module somewhere you need to load this data it needs to talk to an API but it's not your concern where this API is how to locate it in memory what kind of implementation what kind of concrete type I need to talk to you and then from all the methods that implements which one should I call you don't you to be concerned about it I think the word concern is extremely important here that you mentioned because that's what we're doing here we're doing separation of concerns basically and each component like this service or this view controller before they were just concerned about themselves they we're not trying to fit everything in nor have access to everything that's very very important right there and then we can even start having better type safety in forest with an initializer and then you're free to have any limitation we want and it's completely separated from API requests but now how would you test the API client first of all I think that at this point maybe this facade is not even necessary but if he's - needed and you want to test it one way of testing this is to actually make the API requests by invoking load feet and invoking login and having an end-to-end test if you need to guarantee your API is returning exactly what you need but now this is flaky because how can you run this test if you don't have internet connection for example so what you can do it then you can use some libraries like HTTP stubs where it can stop URL sessions and return hard-coded chasten and just check that you're mapping the data correctly whatever type you want and then you can use decodable there's gonna make this work much easier as well and I would even encourage you to have at least a little bit of end-to-end tests especially if you're talking to an API that you don't trust maybe they change the protocols without notifying you it's always nice to have some smoke tests in a separate target that you run occasionally on your CI or before releasing an app just to catch errors with integrating with a real API but all this code here can be tested without actually talking to a real API now the way you can implement this API client if you have this separation here I don't care you can be a single tone you can be global state it can be a capital a single tone a lowercase single tone it can be URL session it can be AF network I don't care as long as my modules are obstructed from that and I'm able to easily replace it I'm able to easily compose those types together I don't care about the API client I have my preferences how I would implement this but then it's a personal preference all I advise people to do is to go to step one two three four or maybe five six I need it just understand how you can go from one step to the other understand the limitation of the singletons we've capitalized with lowercase as make the choices for a current problem you can make single toes testable and it can make lowercase single tones testable as well by appropriately injection if you allow subclasses for example and then you can just transform your single tone into a global mutable state and replace it in tests that's another approach I don't recommend it most of the times it's not gonna pay off but sometimes it pays off so maybe you start there but know that there might be a problem and when the problem occurs you know were to go to the next step and into the next step and then to the next step your code should be soft and that's the essence of software should be able to make those decisions as you go so don't limit your decisions with frameworks or design patterns that's my advice here that's our advice and it's another perk yeah I totally agree with these things I don't know if I answer the question and hopefully we did but we should probably stop here that's a lot to think about I recommend you to take notes from this video and maybe ask more questions try it out yourself try to look at your code right now and draw some diagrams try to find some bottlenecks in there and let us know did we answer your question was that helpful right so yeah practice is the number one thing if you would like something like that to work you need to spend some time testing out these things trying new ideas recognizing what you did wrong in quotes in previous projects and then at some point it will click and some things that you did right in some project is another person wrong for other projects absolutely we should also mention here that if you abstract early you will find yourself trying to fill the gaps by hacking your way and you don't want that right that's why that's why we recommend do not abstract early just let it grow organically let it come when the time is right and it's very easy to have the wrong abstractions as long as it's self-contained in its own wardrobe the damage is also contained yeah but when you have like this massive shared state in the middle where everyone talks to you if you have a mistake the mistake is gonna be huge absolutely cool think that's it perfect okay we hope you enjoyed this video and learned something today don't forget to check the links in the description don't forget to subscribe and I see you next time thanks for watching bye Oh [Music] you
Info
Channel: Essential Developer
Views: 8,673
Rating: undefined out of 5
Keywords: ios, swift, professionalism, pairing, ios development, ios engineering, ios app development, xcode, tdd, modular design, architecture, agile, advanced ios development, tvOS, macOS, advanced swift, clean code, unit testing, testing, best practices, swift framework, pair programming, xctest, screencast, course, solid principles, dependency injection, ui tests, mvvm, tdd for beginners, test view model, ios tdd, singleton, design patterns, dependency inversion ios, dependency inversion
Id: YrLzLiAoOnY
Channel Id: undefined
Length: 33min 12sec (1992 seconds)
Published: Fri Nov 09 2018
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.