diff --git a/src/shared/utils/DependencyManager/DependencyManager.cpp b/src/shared/utils/DependencyManager/DependencyManager.cpp
index fe4d3357452d98ded20b57d0de23be2c188db6a8..df91e884219661e6dd620c72f1c2bf984c5eabfb 100644
--- a/src/shared/utils/DependencyManager/DependencyManager.cpp
+++ b/src/shared/utils/DependencyManager/DependencyManager.cpp
@@ -22,33 +22,26 @@
 
 #include "DependencyManager.h"
 
-#include <cxxabi.h>
 #include <fmt/format.h>
 
 using namespace Boardcore;
 
-// Simple utility function to demangle type infos
-std::string type_name_demangled(const std::type_info& info)
-{
-    char* demangled =
-        abi::__cxa_demangle(info.name(), nullptr, nullptr, nullptr);
-    std::string demangled2{demangled};
-    std::free(demangled);
-
-    return demangled2;
-}
-
 void DependencyManager::graphviz(std::ostream& os)
 {
     os << "digraph {" << std::endl;
 
+    // First print out all of the nodes
+    for (auto& module : modules)
+    {
+        os << fmt::format("  \"{}\"", module.first) << std::endl;
+    }
+
+    // Then print out the arcs
     for (auto& module : modules)
     {
         for (auto& dep : module.second.deps)
         {
-            os << fmt::format("  \"{}({})\" -> \"{}({})\"", module.second.name,
-                              module.second.impl, modules[dep].name,
-                              modules[dep].impl)
+            os << fmt::format("  \"{}\" -> \"{}\"", module.first, dep)
                << std::endl;
         }
     }
@@ -62,9 +55,9 @@ bool DependencyManager::inject()
 
     for (auto& module : modules)
     {
-        LOG_INFO(logger, "Configuring [{}]...", module.second.name);
-        DependencyInjector injector{*this, module.second};
-        module.second.ptr->inject(injector);
+        LOG_INFO(logger, "Configuring [{}]...", module.first);
+        DependencyInjector injector{*this, module};
+        module.second.injectable->inject(injector);
     }
 
     if (load_success)
@@ -79,37 +72,41 @@ bool DependencyManager::inject()
     return load_success;
 }
 
-bool DependencyManager::insertImpl(Injectable* ptr,
-                                   const std::type_info& module_info,
-                                   const std::type_info& impl_info)
+bool DependencyManager::insertImpl(void* raw, Injectable* injectable,
+                                   std::string name)
 {
-    // Early check to see if ptr is nullptr, fail if that's the case
-    if (ptr == nullptr)
-        return false;
-
-    auto idx         = std::type_index{module_info};
-    auto module_name = type_name_demangled(module_info);
-    auto impl_name   = type_name_demangled(impl_info);
-
-    return modules.insert({idx, ModuleInfo{ptr, module_name, impl_name, {}}})
+    return modules.insert({std::move(name), ModuleInfo{raw, injectable, {}}})
         .second;
 }
 
-Injectable* DependencyInjector::getImpl(const std::type_info& module_info)
+void* DependencyManager::getImpl(const std::string& name)
 {
-    auto idx  = std::type_index{module_info};
-    auto iter = manager.modules.find(idx);
-    if (iter == manager.modules.end())
+    auto iter = modules.find(name);
+    if (iter == modules.end())
     {
-        manager.load_success = false;
-
-        std::string module_name = type_name_demangled(module_info);
-        LOG_ERR(logger, "[{}] requires [{}], but the latter is not present",
-                info.name, module_name);
-
         return nullptr;
     }
+    else
+    {
+        return iter->second.raw;
+    }
+}
 
-    info.deps.push_back(idx);
-    return iter->second.ptr;
+void* DependencyInjector::getImpl(const std::string& name)
+{
+    void* ptr = manager.getImpl(name);
+    if (ptr == nullptr)
+    {
+        // Get failed, signal inject failure and log it
+        manager.load_success = false;
+        LOG_ERR(logger, "[{}] requires [{}], which doesn't exist", info.first,
+                name);
+        return nullptr;
+    }
+    else
+    {
+        // Get successful, add it to the dependencies
+        info.second.deps.push_back(name);
+        return ptr;
+    }
 }
diff --git a/src/shared/utils/DependencyManager/DependencyManager.h b/src/shared/utils/DependencyManager/DependencyManager.h
index 4a1826b2574c4c26c57747f12b5768bc904a6349..5f65095b35be0a5d669a49e85e871d6e5ca35a99 100644
--- a/src/shared/utils/DependencyManager/DependencyManager.h
+++ b/src/shared/utils/DependencyManager/DependencyManager.h
@@ -24,6 +24,7 @@
 
 #include <diagnostic/PrintLogger.h>
 
+#include <numeric>
 #include <ostream>
 #include <string>
 #include <typeindex>
@@ -31,6 +32,8 @@
 #include <unordered_map>
 #include <vector>
 
+#include "TypeName.h"
+
 namespace Boardcore
 {
 
@@ -71,8 +74,9 @@ public:
  * class MyDependency1 : public Injectable {};
  *
  * // Abstracting direct dependencies with a common interface
- * class MyDependency2Iface {};
- * class MyDependency2 : public Injectable, public MyDependency2Iface {};
+ * class MyDependency2Iface : public Injectable {};
+ * class MyDependency2 : public
+ * InjectableWithDeps<InjectableBase<MyDependency2Iface>> {};
  *
  * // A simple dependant (which can become a dependency itself)
  * class MyDependant : public InjectableWithDeps<MyDependency1,
@@ -81,9 +85,6 @@ public:
  * DependencyManager dependency_mgr;
  *
  * // Initialize the dependencies
- * MyDependency1 *dep1 = ;
- * MyDependency2Iface *dep2 = new MyDependency2();
- *
  * dependency_mgr.insert<MyDependency1>(new MyDependency1());
  * dependency_mgr.insert<MyDependency2Iface>(new MyDependency2());
  * dependency_mgr.insert<MyDependant>(new MyDependant());
@@ -102,12 +103,12 @@ class DependencyManager
 private:
     struct ModuleInfo
     {
-        Injectable *ptr;
-        // Name of the module interface
-        std::string name;
-        // Name of the actual concrete implementation of this module interface
-        std::string impl;
-        std::vector<std::type_index> deps;
+        void *raw;  ///< Pointer to the dependency's concrete type, returned
+                    ///< when retrieving this dependency
+        Injectable *injectable;  ///< Pointer to the dependency as an
+                                 ///< Injectable, needed for dynamic dispatching
+                                 ///< of the inject method
+        std::vector<std::string> deps;  ///< List of dependencies
     };
 
 public:
@@ -116,14 +117,18 @@ public:
     /**
      * @brief Insert a new dependency.
      *
+     * @note If T is not Injectable the compiler will fail to find this method!
+     *
      * @param dependency Injectable to insert in the DependencyManager.
      * @returns True if successful, false otherwise.
      */
-    template <typename T>
+    template <typename T, typename = std::enable_if_t<
+                              std::is_base_of<Injectable, T>::value>>
     [[nodiscard]] bool insert(T *dependency)
     {
-        return insertImpl(dynamic_cast<Injectable *>(dependency), typeid(T),
-                          typeid(*dependency));
+        return insertImpl(reinterpret_cast<void *>(dependency),
+                          static_cast<Injectable *>(dependency),
+                          Boardcore::typeName<T>());
     }
 
     /**
@@ -142,15 +147,17 @@ public:
     [[nodiscard]] bool inject();
 
 private:
-    [[nodiscard]] bool insertImpl(Injectable *ptr,
-                                  const std::type_info &module_info,
-                                  const std::type_info &impl_info);
+    [[nodiscard]] bool insertImpl(void *raw, Injectable *injectable,
+                                  std::string name);
+
+    void *getImpl(const std::string &name);
 
     Boardcore::PrintLogger logger =
         Boardcore::Logging::getLogger("DependencyManager");
 
     bool load_success = true;
-    std::unordered_map<std::type_index, ModuleInfo> modules;
+    // Maps from interface type name to ModuleInfo
+    std::unordered_map<std::string, ModuleInfo> modules;
 };
 
 /**
@@ -161,8 +168,9 @@ class DependencyInjector
     friend class DependencyManager;
 
 private:
-    DependencyInjector(DependencyManager &manager,
-                       DependencyManager::ModuleInfo &info)
+    DependencyInjector(
+        DependencyManager &manager,
+        std::pair<const std::string, DependencyManager::ModuleInfo> &info)
         : manager(manager), info(info)
     {
     }
@@ -177,17 +185,17 @@ public:
     template <typename T>
     T *get()
     {
-        return dynamic_cast<T *>(getImpl(typeid(T)));
+        return reinterpret_cast<T *>(getImpl(Boardcore::typeName<T>()));
     }
 
 private:
-    Injectable *getImpl(const std::type_info &module_info);
+    void *getImpl(const std::string &name);
 
     Boardcore::PrintLogger logger =
         Boardcore::Logging::getLogger("DependencyManager");
 
     DependencyManager &manager;
-    DependencyManager::ModuleInfo &info;
+    std::pair<const std::string, DependencyManager::ModuleInfo> &info;
 };
 
 namespace DependencyManagerDetails
@@ -251,21 +259,86 @@ struct Contains<T, Type, Types...>
 
 }  // namespace DependencyManagerDetails
 
+template <typename T>
+struct InjectableBase
+{
+};
+
+/**
+ * @brief Base class for an Injectable with dependencies.
+ */
 template <typename... Types>
 class InjectableWithDeps : public Injectable
 {
+protected:
+    /**
+     * Alias of the super class, to be used in derived classes in the
+     * constructor or when overriding methods
+     */
+    using Super = InjectableWithDeps<Types...>;
+
 public:
     virtual void inject(DependencyInjector &injector) override
     {
         storage.inject(injector);
     }
 
-    template <typename T>
+    /**
+     * @brief Get one of the modules in Types.
+     *
+     * @note If T is not inside Types... the compiler will fail to find this
+     * method!
+     */
+    template <typename T,
+              typename = std::enable_if_t<
+                  DependencyManagerDetails::Contains<T, Types...>::value>>
     T *getModule()
     {
-        static_assert(DependencyManagerDetails::Contains<T, Types...>::value,
-                      "Dependency T is not present in the dependencies");
+        return storage.template get<T>();
+    }
+
+private:
+    DependencyManagerDetails::Storage<Types...> storage;
+};
+
+/**
+ * @brief Base class for an Injectable with dependencies and an Injectable
+ * superclass.
+ */
+template <typename Base, typename... Types>
+class InjectableWithDeps<InjectableBase<Base>, Types...> : public Base
+{
+protected:
+    /**
+     * Alias of the super class, to be used in derived classes in the
+     * constructor or when overriding methods
+     */
+    using Super = InjectableWithDeps<InjectableBase<Base>, Types...>;
+
+public:
+    using InjectableSuper = Base;
+    using Base::Base;  ///< Inherit constructors from Base
+
+    static_assert(std::is_base_of<Injectable, Base>::value,
+                  "Base must be Injectable");
+
+    virtual void inject(DependencyInjector &injector) override
+    {
+        Base::inject(injector);
+        storage.inject(injector);
+    }
 
+    /**
+     * @brief Get one of the modules in Types.
+     *
+     * @note If T is not inside Types... the compiler will fail to find this
+     * method!
+     */
+    template <typename T,
+              typename = std::enable_if_t<
+                  DependencyManagerDetails::Contains<T, Types...>::value>>
+    T *getModule()
+    {
         return storage.template get<T>();
     }
 
diff --git a/src/shared/utils/DependencyManager/TypeName.h b/src/shared/utils/DependencyManager/TypeName.h
new file mode 100644
index 0000000000000000000000000000000000000000..0d7f10ff64c071919b2c55b1c08582c70fe06b63
--- /dev/null
+++ b/src/shared/utils/DependencyManager/TypeName.h
@@ -0,0 +1,80 @@
+/* Copyright (c) 2024 Skyward Experimental Rocketry
+ * Authors: Davide Mor
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <string>
+
+namespace Boardcore
+{
+
+namespace TypeNameDetails
+{
+
+// This needs to be in its own namespace so that types in Boardcore:: don't get
+// the namespace cut off
+
+template <typename T>
+std::string typeName()
+{
+
+    // taken from
+    // https://github.com/Manu343726/ctti/blob/master/include/ctti/detail/pretty_function.hpp
+
+#if defined(__clang__)
+// clang required slightly different logic, but should be supported, keep it
+// like this for now
+#error "Clang does not yet support typeName"
+#elif defined(__GNUC__) || defined(__GNUG__)
+#define SKYWARD_PRETTY_FUNCTION __PRETTY_FUNCTION__
+#define SKYWARD_PRETTY_FUNCTION_PREFIX \
+    "std::string Boardcore::TypeNameDetails::typeName() [with T = "
+#define SKYWARD_PRETTY_FUNCTION_SUFFIX \
+    "; std::string = std::__cxx11::basic_string<char>]"
+#else
+#error "Compiler does not support typeName"
+#endif
+
+#define SKYWARD_PRETTY_FUNCTION_PREFIX_LEN \
+    (sizeof(SKYWARD_PRETTY_FUNCTION_PREFIX) - 1)
+#define SKYWARD_PRETTY_FUNCTION_SUFFIX_LEN \
+    (sizeof(SKYWARD_PRETTY_FUNCTION_SUFFIX) - 1)
+
+    std::string pretty_function{SKYWARD_PRETTY_FUNCTION};
+    return std::string{
+        pretty_function.begin() + SKYWARD_PRETTY_FUNCTION_PREFIX_LEN,
+        pretty_function.end() - SKYWARD_PRETTY_FUNCTION_SUFFIX_LEN};
+
+#undef SKYWARD_PRETTY_FUNCTION
+#undef SKYWARD_PRETTY_FUNCTION_PREFIX
+#undef SKYWARD_PRETTY_FUNCTION_SUFFIX
+#undef SKYWARD_PRETTY_FUNCTION_PREFIX_LEN
+#undef SKYWARD_PRETTY_FUNCTION_SUFFIX_LEN
+}
+
+}  // namespace TypeNameDetails
+
+template <typename T>
+std::string typeName()
+{
+    return TypeNameDetails::typeName<T>();
+}
+
+}  // namespace Boardcore
\ No newline at end of file
diff --git a/src/tests/catch/test-dependencymanager.cpp b/src/tests/catch/test-dependencymanager.cpp
index 863e87f5123f1529c438a5d2e783b12a60ba7866..04989e34eaf9b3e4047088ee96f478c96d7b7156 100644
--- a/src/tests/catch/test-dependencymanager.cpp
+++ b/src/tests/catch/test-dependencymanager.cpp
@@ -23,6 +23,7 @@
 #include <utils/DependencyManager/DependencyManager.h>
 
 #include <catch2/catch.hpp>
+#include <iostream>
 
 using namespace Boardcore;
 
@@ -46,7 +47,7 @@ private:
     bool value = false;
 };
 
-class B : public Injectable
+class B : public InjectableWithDeps<A>
 {
 public:
     B() {}
@@ -55,21 +56,18 @@ public:
 
     bool bong_b() { return value; }
 
-    void inject(DependencyInjector &getter) { a = getter.get<A>(); }
-
 private:
-    A *a       = nullptr;
     bool value = false;
 };
 
-class CIface
+class CIface : public Injectable
 {
 public:
     virtual void bing_c() = 0;
     virtual bool bong_c() = 0;
 };
 
-class C : public CIface, public InjectableWithDeps<A, B>
+class C : public InjectableWithDeps<InjectableBase<CIface>, A, B>
 {
 public:
     void bing_c() override
@@ -83,21 +81,81 @@ private:
     bool value = false;
 };
 
-class D : public Injectable
+class D : public InjectableWithDeps<CIface>
 {
 public:
-    void bing_d() { value = c->bong_c(); }
+    void bing_d() { value = getModule<CIface>()->bong_c(); }
 
     bool bong_d() { return value; }
 
-    void inject(DependencyInjector &getter) { c = getter.get<CIface>(); }
-
 private:
-    CIface *c  = nullptr;
     bool value = false;
 };
+
+class E : public Injectable
+{
+public:
+    int get_answer() { return 42; }
+};
+
+class F : public Injectable
+{
+public:
+    int get_true_answer() { return 69; }
+};
+
+class G : public InjectableWithDeps<E>
+{
+public:
+    virtual int get_truest_answer() { return getModule<E>()->get_answer(); }
+};
+
+class H : public InjectableWithDeps<InjectableBase<G>, F>
+{
+public:
+    int get_truest_answer() override
+    {
+        return getModule<F>()->get_true_answer() + G::get_truest_answer();
+    }
+};
+
+class I : public InjectableWithDeps<G>
+{
+public:
+    int get_ultimate_true_answer()
+    {
+        return getModule<G>()->get_truest_answer();
+    }
+};
+
+namespace Inner
+{
+template <typename T, typename U>
+struct Pair
+{
+};
+}  // namespace Inner
+
 }  // namespace Boardcore
 
+TEST_CASE("DependencyManager - TypeName")
+{
+
+    REQUIRE(Boardcore::typeName<Boardcore::A>() == "Boardcore::A");
+    REQUIRE(Boardcore::typeName<Boardcore::B>() == "Boardcore::B");
+    REQUIRE(Boardcore::typeName<Boardcore::CIface>() == "Boardcore::CIface");
+    REQUIRE(Boardcore::typeName<Boardcore::C>() == "Boardcore::C");
+    REQUIRE(Boardcore::typeName<Boardcore::D>() == "Boardcore::D");
+    REQUIRE(Boardcore::typeName<Boardcore::E>() == "Boardcore::E");
+    REQUIRE(Boardcore::typeName<Boardcore::F>() == "Boardcore::F");
+    REQUIRE(Boardcore::typeName<Boardcore::G>() == "Boardcore::G");
+    REQUIRE(Boardcore::typeName<Boardcore::H>() == "Boardcore::H");
+    REQUIRE(Boardcore::typeName<Boardcore::I>() == "Boardcore::I");
+    REQUIRE(Boardcore::typeName<
+                Boardcore::Inner::Pair<Boardcore::A, Boardcore::B>>() ==
+            "Boardcore::Inner::Pair<Boardcore::A, Boardcore::B>");
+}
+
 TEST_CASE("DependencyManager - Circular dependencies")
 {
     DependencyManager manager;
@@ -105,8 +163,8 @@ TEST_CASE("DependencyManager - Circular dependencies")
     Boardcore::A *a = new Boardcore::A();
     Boardcore::B *b = new Boardcore::B();
 
-    REQUIRE(manager.insert<Boardcore::A>(a));
-    REQUIRE(manager.insert<Boardcore::B>(b));
+    REQUIRE(manager.insert(a));
+    REQUIRE(manager.insert(b));
     REQUIRE(manager.inject());
 
     a->bing_a(true);
@@ -120,6 +178,8 @@ TEST_CASE("DependencyManager - Circular dependencies")
 
     b->bing_b(false);
     REQUIRE(!b->bong_b());
+
+    manager.graphviz(std::cout);
 }
 
 TEST_CASE("DependencyManager - Virtual Dependencies")
@@ -152,6 +212,8 @@ TEST_CASE("DependencyManager - Virtual Dependencies")
     REQUIRE(c->bong_c());
     d->bing_d();
     REQUIRE(d->bong_d());
+
+    manager.graphviz(std::cout);
 }
 
 TEST_CASE("DependencyManager - Inject fail")
@@ -160,6 +222,37 @@ TEST_CASE("DependencyManager - Inject fail")
 
     Boardcore::A *a = new Boardcore::A();
 
-    REQUIRE(manager.insert<Boardcore::A>(a));
+    REQUIRE(manager.insert(a));
     REQUIRE_FALSE(manager.inject());
 }
+
+TEST_CASE("DependencyManager - Insert two instances fail")
+{
+    DependencyManager manager;
+
+    Boardcore::A *a1 = new Boardcore::A();
+    Boardcore::A *a2 = new Boardcore::A();
+
+    REQUIRE(manager.insert(a1));
+    REQUIRE_FALSE(manager.insert(a2));
+}
+
+TEST_CASE("DependencyManager - Dependency tree")
+{
+    DependencyManager manager;
+
+    Boardcore::E *e = new Boardcore::E();
+    Boardcore::F *f = new Boardcore::F();
+    Boardcore::H *h = new Boardcore::H();
+    Boardcore::I *i = new Boardcore::I();
+
+    REQUIRE(manager.insert(e));
+    REQUIRE(manager.insert(f));
+    REQUIRE(manager.insert<Boardcore::G>(h));
+    REQUIRE(manager.insert(i));
+    REQUIRE(manager.inject());
+
+    REQUIRE(i->get_ultimate_true_answer() == 111);
+
+    manager.graphviz(std::cout);
+}
\ No newline at end of file