Joshua's Docs - General Programming Concepts

Resources

What & Link Type
My Favorite Sources (Links to other cheatsheets and dev learning resources) Collection

Dependency Injection

Dependence Injection, often just referred to as "DI", is a common concept in OOP languages, and especially in frameworks. It tends to get over-complicated, but a basic view is this:

Rather than your class/object creating the thing it depends on (DB Driver, Session Manager, etc), it takes it as part of its setup (constructor or setter).

This reversal of who is in control is also why DI is often referred to as a type of Inversion of Control Container or IoC.

Resources for learning:

  • Best video on the topic (under 5 minutes!) - YouTube
  • Excellent S/O Answer - S/O

In practical use:

Most often, DI in OOP means that your class takes another class, either as an argument in the constructor, or via a setter function. That's it in a nutshell.

class User {
	manager: UserManager;
	
	// This is the important part:
	constructor(usrMgr: UserManager){
		// Notice we are *not* using the "new" keyword; we take an already instantiated class/object
		this.manager = usrMgr;
	}

	public updateName(name: string){
		this.manager.updateInDb(name);
	}
}

Why it matters / importance

There are a bunch of reasons why DI is popular and has a significant impact on how a program works.

  • Isolation and Decoupling: Your code does not need to know the implementation details of the things it relies on
    • All your code cares about is the interface and not the actual implementation details
    • This means that dependencies can be easily swapped out without your code caring
    • If the implementation and/or construction of the depenency changes, your code is not affected (as long as the interface stays the same)!
  • Complexity: The isolation that DI provides and level of abstraction means that your own code can be less complex
    • Your class doesn't need as much knowledge about the things it uses
  • Frameworks: This really goes back to the isolation and decoupling benefits, but in a framework, DI means that your class can be auto-injected with complex framework pieces without caring about the details
    • Also important as Frameworks update behind-the-scenes code; you don't have to update your classes, even if dependency stuff changes, because you are no longer responsible for creating those dependencies!
    • Frameworks also often provide Dependency Injection Containers (more on that later)
  • Testing: Complex classes can be more easily mocked (and injected) for automated testing
    • If your class depends on another class, which depends on another, which depends on... that is going to get very complicated to manually mock
    • With DI, in your tests, you can inject mocked versions of the things you need.
    • Again, since DI just cares about the interface, the mocked dependency does not need to be "full-featured"
  • Isolation, Decoupling, Seperation of Concerns, and the Dependency Inversion Principle:
    • In my mind, these all say similar things, and kind of wrap up the benefits of everything above.
    • In short, to me, DI is all about moving the responsibility of required links.
      • Without DI, you are creating links to what you need inside your class logic, and you have to know how to create the things you need before you chain yourself to them. Not only that, but you have to know where to attach multiple chains at multiple points.
      • With DI, other code is handing you exactly what you need with a handy leash already attached.

Dependency Injection Container

This is more advanced, so stop reading and review the previous sections if you don't yet understand the basics of DI.

DI, without any special setup, means that you still need some special setup, you are just reducing the amount of setup required and where it happens. But still, if your class has dependencies, then to construct your classes, the code calling it needs to provide the thing to be injected. If you have lots of things to inject, or they are highly complicated, this can make DI still complicated.

Dependency Injection Containers are a way to simplify this mapping of what your class needs to its creation. Essentially a container is responsible for:

  • Tracking what you need injected
  • Injecting it
  • Reduce overhead (usually): re-use dependency if it already exists before injecting (singleton pattern)

Examples and further reading:


MVC (Model-View-Controller)

Resources

What & Link Type
Chrome Dev - App Frameworks - MVC Guide
Essential JS Design Patterns - MVC Section Ebook Section
TodoMVC - Examples of MVC pattern in various frameworks / langs Examples
Node Express - official example Example

Diagrams

Quick overview

A pattern for separating code by area of concern, which can be represented at its most basic level by this structure:

Wikipedia MVC Diagram

  • Model
    • Represents the business data and logic that is specific to your application
    • In optimal cases, updates to the model automatically notify observers, e.g. the View
    • This is the only layer that should directly interact with persistent data (e.g. database, file store, etc).
  • Controller
    • Layer that acts as worker / glue between Model and View
      • Another way to think of this is as a request broker
    • Pass updates in Model to view, and pass requests from View to modify data (CRUD) to Model
  • View
    • The "presentation" layer, only concerned with the final display output

Where do routes fit in?

TLDR: Routes are another entrypoint to controllers, like Views, and should pass the request to the appropriate controller methods, while optionally performing some request logic and/or formatting the response.

Routes should not directly modify business data or perform business logic.

In a lot of web applications, you deal with (virtual) routing, where your file structure does not mirror the URL in the browser, but your app routes the request to the right code. In an OOP lang/framework, you might have entirely separate classes and methods for routing.

In general, routes are coupled fairly closely with controllers, but should be considered a distinct layer, which can sit between the browser and the controller, just like View.

In terms of actual code, routes are mainly responsible for just calling controller methods, so it is not uncommon to see something that is just a wrapper around controller methods:

Pseudo-code:

router.post('/user', (req, res) => {
	return UserRouter.create();
})

// Could be separate file
const userControl = new UserController();
class UserRouter {
	public static create(req, res) {
		return userControl.create(req);
	}
}

However, routes can also contain some logic, but you should be careful to keep business data logic in Models. The kind of logic that is common to see in routes, and is OK, is things like:

  • Checking for authentication
  • Request validation
  • Request throttling / blocking
  • Request standardization
  • Response formatting
    • Formatting the data from the Model layer before returning
    • Example: Converting to JSON
  • Many, many, more examples

More resources


Library file types

Stat/Dyn OS MinGW MSVC
Static Win / Unix .a .lib
Dynamic Win .dll .dll
Dynamic Unix .so NA

.DLL = Dynamic-Link Library. Windows specific

.so = Shared Object. Linux equivalent of DLL

.a | .lib = Static library file

You can break down these types into the two major categories:

  • Declarative:
    • .a, .lib
    • Header (.h) files count as these too
    • The key is that these files declare, or describe things (classes, functions, members, etc), but do not actually have the code to run them.
  • Implementation:
    • .dll, .so
    • .cpp is the main one everyone knows
    • The key is that these files actually contain the implementation code - the code that actually "does things", like functions - as opposed to those files that describe them

Additional reading:

Finding DLL Dependents

There are several tools on Windows that you can use to try and figure out which DLLs a program is dependent on:


Big O Notation

Cheatsheets

What is Big O Notation

"Big-O notation is a relative representation of the complexity of an algorithm."

A more results-driven answer is that Big O Notation is a representation of how well, on the basis of few operations, an algorithm scales based on input size alone.

Quick example:

A short example is simply echoing back an array, versus logging something special for each input. E.g.:

  • const myAlg = (a) => console.log(a) is O(1)
  • const myAlg = (a) => a.forEach((e,i) => {console.log('Value = ' + e + ', Index = ' + i)}) is O(n)

The above example ignores the overhead of console.log() and makes some probably bad assumptions, and really is just for illustrative purposes.

Table

Complexity Common Name N=1 N=10 N=100 Common Examples
O(1) Constant 1 1 1 Picking an element off an array

getLast = (a) => a[a.length-1];
O(log N) Logarithmic 0 2 5 Binary search!

NOTE: The base is not specified for log, but it also doesn't really matter due to Big O being about relative scaling.
O(N) Linear 1 10 100 Standard for loop

echoArr = (a) => a.forEach(e => console.log(e))
O(N log N) Linearithmic / Loglinear 0 20 461 "Divide and Conquer" type algorithms. For example, Merge Sort. Also FFT.

Imagine doing binary search on both sides at the same time...
O(N²) Quadratic 1 100 10000 for loop inside a for loop - recursion
O(2ᶰ) Exponential 1 1024 1267650600228229401496703205376 Brute force, recursion, calculating Fibonacci numbers, etc.
O(N!) Factorial 1 3628800 9.3e+157 (HUGE!) Solving the "Traveling Salesman Problem" with brute force

Also, bad programming, like BOGO sort lol

Where const a is an array.

Adding vs Multiplying vs Multiple Terms

Basic rules are:

  • Loop inside loop = multiply
  • Loop outside loop = add
    • But see notes on dropping all but highest

Important reminders:

  • Equal O Notation does not mean equal performance - it just means the performance scales the same way
    • Given function A with O(n) and function B with O(n), function B might actually outperform function A by hours, but both scale in the same way (linear) with input
    • This is also why you drop multipliers - like O(2n) is really just O(n), because you are interested in relative performance, where having a constant 2 is irrelevant information
      • This is sometimes called "dropping non-dominate terms"
  • A single bottleneck can raise the Big O complexity of an entire process; you can't go faster than the slowest point
    • Always take the worst-case as the value
    • Technically, it is more like adding them (at least the time), but given how the scales compare, usually the least performant is chewing up the majority
      • Kind of like how if you are paying $/foot of a cable, they probably don't include sales tax, given how the majority of your total price is not made up by it
  • n is just a placeholder variable, for input
    • Your input could be anything and everything

Algorithms

Algorithms - Resources / Cheatsheets

Also relevant: Job Search: Interview Challenges / Prep

Partial breakdown

  • Binary Search: O(log N)
    • Resources:
    • How it works:
      • Binary search requires that the input is already sorted!

      • Basics:
        • Start with the middle of the input. Divide in half, and discard the half that the input cannot lie in
        • Keep splitting in half
  • @TODO: Add more...
Markdown Source Last Updated:
Wed Jun 30 2021 06:42:54 GMT+0000 (Coordinated Universal Time)
Markdown Source Created:
Sun Sep 22 2019 22:17:35 GMT+0000 (Coordinated Universal Time)
© 2024 Joshua Tzucker, Built with Gatsby
Feedback