The layer is the real workhorse
Subclassing a Layer is far more common than subclassing a whole Model — it's the recommended building block, because a custom layer drops straight into Sequential, Functional, or another subclassed model without ceremony. A layer has three methods worth knowing: __init__() stores configuration (how many units, which activation), build() creates the weights, and call() runs the forward pass (see the Code block).
Why weights live in build(), not __init__()
The interesting one is build(input_shape). You could create weights in __init__(), but then you'd have to know the input's feature dimension up front and hardcode it — brittle the moment you reuse the layer somewhere else. build() is Keras's answer: it runs automatically the first time the layer sees real data, receives the concrete input_shape, and sizes the weight matrix from input_shape[-1]. This is lazy weight creation, and it's why you can write MyDense(64) and have it adapt to whatever feeds it. Each weight is registered with add_weight(), which is what makes Keras track it as a trainable variable.
get_config() closes the loop on serialization: it returns the constructor arguments as a plain dict so Keras can rebuild the layer from a saved file. Skip it and your custom layer won't round-trip through model.save() / load_model().