First we tell the compiler how to nme the output files, just to avoid misunderstanding:
main.o: main.cpp
g++ -c main.cpp -o main.o
factorial.o: factorial.cpp
g++ -c factorial.cpp -o factorial.o
hello.o: hello.cpp
g++ -c hello.cpp -o hello.o
Then we put in automatic variables, to reduce redundancy:
main.o: main.cpp
g++ -c $< -o $@
factorial.o: factorial.cpp
g++ -c $< -o $@
hello.o: hello.cpp
g++ -c $< -o $@
Then we realize that these rules all look the same, so we combine them as a static pattern rule:
main.o factorial.o hello.o: %.o : %.cpp
g++ -c $< -o $@
Then we use a variable to store the names of the objects:
OBJECTS := main.o factorial.o hello.o
$(OBJECTS): %.o : %.cpp
g++ -c $< -o $@